#matlab #csv #scanf #fopen #low-level-io
#matlab #csv #сканф #приоткрыть #низкоуровневый ввод-вывод
Вопрос:
Я действительно пытаюсь понять, как я могу импортировать данные CSV, которые содержат 9 столбцов и около 400 строк данных, в таблицу в рабочей области Matlab. Это было бы легко, если бы мне разрешили использовать встроенные наборы инструментов, которые предлагает Matlab, но вместо этого мне нужно попытаться выполнить задачу, используя только такие команды, как fscanf, fopen и т.д. Сами данные представлены в смешанных форматах для каждого столбца. Десятичные, с плавающей запятой, строковые и т.д. Мне также разрешено использовать CSVread, но мне не удалось заставить это работать, поскольку, насколько я понимаю, CSVread работает только с числовыми значениями.
Вот мой код:
>> filename = 'datatext.csv'; %The file in CSV format
>> fid = fopen(filename);
>> headers = fgetl(fid); %using fgetl to remove the headers and put them in a variable
Я использовал fgetl, чтобы пропустить строку заголовков файла и добавить их в свою собственную переменную, однако я не уверен, куда идти с этого момента при создании таблицы. По сути, я хочу получить таблицу размером 400 строк на 9 столбцов в рабочей области Matlab.
Вот пример нескольких строк того, как выглядит текстовый файл:
18,8,318,150,3436,11,70,1,sampletext
16,8,304,150,3433,12,70,1,sampletext2
Я предполагаю, что мне придется использовать встроенные функции преобразования некоторых ячеек, что мне разрешено делать. Вероятно, я пропустил какую-то важную информацию, чтобы получить правильную помощь, но любая помощь, которую кто-либо может оказать, будет высоко оценена. Спасибо.
Комментарии:
1.
readtable
не требует никакого набора инструментов, но если вам нужно придерживаться только ввода-вывода низкого уровня, вы действительно можете использоватьfscanf
илиtextscan
2. Спасибо, забыл добавить, что textscan специально запрещен, однако я постараюсь работать с fscanf.
Ответ №1:
Функции самого низкого уровня для чтения файла (как описано в разделе Импорт текстовых файлов данных с помощью низкоуровневого ввода-вывода):
- fscanf, который считывает форматированные данные в текстовом или ASCII-файле; то есть файл, который вы можете просмотреть в текстовом редакторе. Дополнительные сведения см. в разделе Чтение данных в формате шаблона.
- fgetl и fgets, которые одновременно считывают одну строку файла, где символ новой строки разделяет каждую строку. Для получения дополнительной информации см. раздел Построчное чтение данных.
- fread, который считывает поток данных на байтовом или битовом уровне. Дополнительные сведения см. в разделе Импорт двоичных данных с помощью ввода-вывода низкого уровня.
В вашем случае входной файл является ascii, а не двоичным, поэтому мы можем сразу отказаться от последнего варианта ( fread
).
У вас остается fgetl/fgets
(используется для чтения файла построчно, а затем для анализа каждой строки) и fscanf
.
У вас уже есть два ответа с использованием построчного подхода, поэтому я не буду подробно описывать этот, а скорее покажу вам, как вы можете использовать fscanf
(поскольку ваши данные подходят, они действительно организованы в форматированный шаблон).
Преимущество использования fscanf
заключается в том, что при условии, что вы используете правильный formatSpec
параметр, функция сможет прочитать весь файл за один раз, вместо того, чтобы перебирать построчно. Это верно для всех числовых данных в вашем файле. Нам нужно будет выполнить второй проход для текстовых элементов в вашем последнем столбце.
Определение спецификаторов формата:
сначала давайте определим ваши спецификации формата. Мы будем использовать другой формат для каждого прохода. Первый проход считывает все числовые данные, но пропускает текстовые поля, в то время как второй проход делает обратное, игнорирует все числовые данные и читает только текстовые поля. '*'
Символ действительно полезен для этого при определении спецификатора формата:
DataFormatSpec = repmat('%d,',1,8) ;
DataFormatSpec = [DataFormatSpec '%*s'] ; % yield: '%d,%d,%d,%d,%d,%d,%d,%d,%*s'
TextFormatSpec = repmat('%*d,',1,8) ;
TextFormatSpec = [TextFormatSpec '%s'] ; % yield: '%*d,%*d,%*d,%*d,%*d,%*d,%*d,%*d,%s'
Я использовал %d
для всех столбцов, потому что в ваших образцах данных я не видел разнообразия числовых типов, о которых вы упомянули. Вы можете легко заменить это на %f
, если этого требуют данные, и вы можете без проблем смешивать оба типа в одном и том же спецификаторе (если все они числовые). Просто используйте то, что имеет смысл для каждого столбца.
Вооружившись этим, давайте перейдем к файлу. Данные находятся в формате шаблона после заголовка, поэтому сначала нам нужно пройти строку заголовка перед вызовом fscanf
. Мы сделаем это так, как вы это сделали:
%% Open file and retrieve header line
fid = fopen( filein,'r') ;
hdr = fgetl(fid) ; % Retrieve the header line
DataStartIndex = ftell(fid) ; % save the starting index of data section
Вызов ftell
позволяет нам сохранить положение указателя файла. После того, как мы прочитаем заголовок, указатель будет расположен прямо в начале раздела данных. Мы сохраняем его, чтобы иметь возможность перемотать указатель файла на ту же точку для второго прохода чтения.
Чтение числовых данных:
Это делается очень быстро с помощью fscanf
простого вызова:
%% First pass, read the "numeric values"
dataArray = fscanf( fid , DataFormatSpec , [8 Inf]).' ;
Обратите внимание на оператор транспонирования .'
в конце строки. Это связано с тем, что fscanf
заполнение значений, которые он считывает, выполняется в основном порядке столбцов, но данные в текстовом файле считываются в основном порядке строк. Операция транспонирования в конце заключается в том, чтобы просто получить выходной массив того же размера, что и в текстовом файле.
Теперь dataArray
содержит все ваши числовые данные:
>> dataArray
dataArray =
18 8 318 150 3436 11 70 1
16 8 304 150 3433 12 70 1
Чтение текстовых данных:
Это было немного сложнее. fscanf
автоматическое преобразование текстовых символов в их значение ascii. Достаточно просто преобразовать их обратно в фактические символы (с помощью функции char()
). Самым большим препятствием является то, что если мы прочитаем все текстовые поля за один раз, все они будут отображаться как последовательность чисел, но не будет способа узнать, где заканчивается каждая строка и где начинается следующая. Чтобы преодолеть это, мы будем выполнять построчное чтение, но по-прежнему использовать fscanf
:
%% Second pass, read the "text" values
fseek(fid,DataStartIndex,'bof') ; % Rewind file pointer to the start of data section
nRows = size(dataArray,1) ; % How many lines we'll need to read
textArray = cell(nRows,1) ; % pre allocate a cell array to receive the text column elements
for iline=1:nRows
textArray{iline,1} = char( fscanf(fid,TextFormatSpec,1).' ) ;
end
fclose(fid) ; % Close file
Обратите внимание еще раз на использование оператора transpose .'
и использование char()
. Теперь textArray
это массив ячеек, содержащий все ваши текстовые поля:
>> textArray
textArray =
'sampletext'
'sampletext2'
Перегруппировка набора данных:
Лично я бы разделил два массива, поскольку они являются наиболее оптимизированными контейнерами для каждого типа данных ( double
массив для числовых данных и cell
массив для массива строк). Однако, если вам нужно перегруппировать их в единую структуру данных, вы можете использовать массив ячеек:
%% Optional, merge data into cell array
FullArray = [num2cell(dataArray) textArray]
FullArray =
[18] [8] [318] [150] [3436] [11] [70] [1] 'sampletext'
[16] [8] [304] [150] [3433] [12] [70] [1] 'sampletext2'
Или вы могли бы использовать table
:
%% Optional, merge data into a table
T = array2table(dataArray) ;
T.text = textArray ;
T.Properties.VariableNames = [cellstr(reshape(sprintf('v%d',1:8),2,[]).') ; {'text'}] ;
Что дает:
T =
v1 v2 v3 v4 v5 v6 v7 v8 text
__ __ ___ ___ ____ __ __ __ _____________
18 8 318 150 3436 11 70 1 'sampletext'
16 8 304 150 3433 12 70 1 'sampletext2'
Очевидно, что если вы выберете версию таблицы, используйте имена переменных, которые вы проанализировали из заголовка, вместо автоматически сгенерированного, который я использовал в этом примере.
Ответ №2:
fgetl()
Многократное использование для извлечения строк текстового файла и разделения содержимого строк с помощью split()
Не уверен, разрешено ли использование split()
функции после чтения содержимого текстового файла, но вот реализация, которая использует fgetl()
для захвата содержимого текстового файла построчно с помощью цикла. После извлечения всех строк при использовании split()
со вторым аргументом разделитель, установленный на запятую ,
, позволяет разделить содержимое на ячейки. Первый цикл for-loop извлекает содержимое текстового файла построчно и сохраняет его в вызываемой строке Lines
. Второй цикл for разделяет строки, сохраненные в Lines
разделителе ,
, позволяя сохранять ячейки в другом массиве строк, показанном ниже, с разделением содержимого. Здесь -1
указывается ложная запись, полученная / при достижении конца файла.
Sample.txt
18,8,318,150,3436,11,70,1,sampletext
16,8,304,150,3433,12,70,1,sampletext2
Сценарий:
Text = "Start";
% open file (read only)
fileID = fopen('Sample.txt', 'r');
%Running for loop till end of file termination "-1"%
Line_Index = 1;
while(Text ~= "-1")
% read line/row
Text = string(fgetl(fileID));
% stopping criterion
if (Text ~= "-1")
Lines(Line_Index,1) = Text;
end
% update row index
Line_Index = Line_Index 1;
end
% close file
fclose(fileID);
[Number_Of_Lines,~] = size(Lines);
Output_Array = strings(Number_Of_Lines,9);
for Row_Index = 1: Number_Of_Lines
Line = split(Lines(Row_Index,:),',');
Line = Line';
Output_Array(Row_Index,:) = string(Line);
end
Запускался с использованием MATLAB R2019b
Комментарии:
1. Большое вам спасибо за оба ответа. Оба были чрезвычайно полезны, и я применил их к своей проблеме.
Ответ №3:
Хотя ответ @MichaelTr7 в полном порядке, я хотел бы предложить немного более подробный ответ, включая преобразование в типы и, в конечном итоге, возврат таблицы. Обратите внимание, что он также включает предварительное выделение переменных. Поскольку MATLAB хранит переменные в виде последовательных блоков в оперативной памяти, полезно заранее сообщить, насколько большой будет ваша переменная. (MATLAB на самом деле жалуется на переменные, которые, по-видимому, растут в цикле …)
Решение также основывается на fgetl
и (позже) split
cell2table
(это определенно больше не низкоуровневая функция, но в вашем случае это может быть нормально, поскольку оно больше не обрабатывает чтение)
% USER-INPUT
FileName = 'Sample.csv';
strType = "%l,%f,%d,%f,%f,%f,%f,%f,%s";
delimiter = ",";
% allocate strings
Data = strings(100,1);
% open file (read only)
fileID = fopen(FileName, 'r');
%Running for loop till end of file termination "-1"%
Line_idx = 1;
while true
% read line/row
Line_text = string(fgetl(fileID));
% stopping criterion
if (Line_text == "-1")
break
end
Data(Line_idx,1) = Line_text;
% update row index
Line_idx = Line_idx 1;
% extend allocation
if Line_idx > size(Data,1)
Data = [Data;strings(100,1)]; %#ok<AGROW>
end
end
% close file
fclose(fileID);
% crop variable/rows
Data = Data(1:Line_idx-1,:);
strType_splt = split(strType, delimiter);
Num_strType = length( strType_splt );
Num_Data = length( split(Data(1,:), delimiter) );
% check number of conversion types
assert( Num_strType == Num_Data, strcat("Conversion format 'strType' has not the same number of values as the file ",FileName,"."))
% allocate cell
C = cell(size(Data,1),Num_strType);
% loop over rows amp; columns convert the elements
for r = 1:size(Data,1) % loop over rows
line = Data(r);
% split into individual strings
line_splt = split(line, delimiter);
for c = 1:Num_strType % loop over columns
element_str = line_splt(c);
type = strType_splt(c);
C{r,c} = convertStr( type, element_str );
end
end
% create table
T = cell2table(C);
function element = convertStr(type,str)
switch type
case "%f" % float = single | convert to double and cast to single
element = single(str2double(str));
case "%l" % long float
element = str2double(str);
case "%d" % convert to double and cast to integer
element = int32(str2double(str));
case "%s"
element = string(str);
case "%D" % non-standard: datetime
element = datetime(str);
otherwise
element = {str};
end
end
Это предполагает наличие файла Sample.csv, например, со следующим содержимым:
18,8,318,150,3436,11,70,1,sampletext
16,8,304,150,3433,12,70,1,sampletext2