如何仅使用低级 I/O 命令将 CSV 数据导入 Matlab
How do I import CSV data in to Matlab only using low level I/O commands
我真的很想弄清楚如何将包含 9 列和大约 400 行数据的 CSV 数据导入到 Matlab 工作区的 table 中。如果我被允许使用 Matlab 必须提供的 built-in 工具箱,这将很容易,但相反,我需要尝试仅使用 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 跳过文件的 headers 行并将它们添加到它们自己的变量中,但是我不确定从这一点开始创建 table。基本上我想在 Matlab 的工作区中实现 400 行乘 9 列 table。
下面是文本文件的几行示例:
18,8,318,150,3436,11,70,1,sampletext
16,8,304,150,3433,12,70,1,sampletext2
我假设我将不得不使用某些单元格的 built-in 转换函数,这是我被允许的。为了获得正确的帮助,我可能错过了一些重要信息,但是任何人提供的任何帮助都将不胜感激。谢谢。
使用fgetl()
重复检索文本文件行并使用split()
分隔行内容
不确定是否允许在读取文本文件内容后使用 split()
函数,但这里有一个使用 fgetl()
通过循环逐行抓取文本文件内容的实现。在使用 split()
和第二个参数检索所有行后,分隔符设置为逗号 ,
允许将内容拆分为单元格。第一个 for 循环逐行检索文本文件的内容并将其存储在名为 Lines
的字符串中。第二个 for 循环通过分隔符 ,
拆分存储在 Lines
中的字符串,允许将单元格存储在另一个字符串数组中,如下所示,内容分开。这里 -1
表示错误输入 retrieved/when 已到达文件末尾。
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
虽然@MichaelTr7 的回答非常好,但我想建议一个更详细的答案,包括转换为类型并最终返回 table。
请注意,它还包括 预分配 变量。因为 MATLAB 将变量存储在 RAM 中的一致块中,所以最好事先告诉它你的变量有多大。 (MATLAB 实际上抱怨似乎在循环中增长的变量...)
该解决方案也建立在 fgetl
and (later) 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 & 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
读取文件的最低级别函数是,
(如 Import Text Data Files with Low-Level I/O 中所述):
fscanf, which reads formatted data in a text or ASCII file; that is, a file you can view in a text editor. For more information, see Reading Data in a Formatted Pattern.
fgetl and fgets, which read one line of a file at a time, where a newline character separates each line. For more information, see Reading Data Line-by-Line.
fread, which reads a stream of data at the byte or bit level. For more information, see Import Binary Data with Low-Level I/O.
在你的情况下,输入文件是 ascii,而不是二进制文件,所以我们可以立即删除最后一个选项 (fread
)。
剩下 fgetl/fgets
(用于逐行读取文件然后解析每一行)和 fscanf
.
你已经有了两个使用逐行方法的答案,所以我不会详细说明这个,而是向你展示如何使用 fscanf
(因为你的数据是 suitable,它们确实以 格式化模式 ).
组织
使用fscanf
的好处是,如果你使用正确的formatSpec
参数,函数将能够一次读取整个文件,而不是逐行迭代。
对于文件中的所有 numeric 数据都是如此。我们将不得不对您最后一列中的文本元素进行第二次传递。
定义格式说明符:
首先让我们定义您的格式规范。我们将为每个通道使用不同的格式。第一遍将读取所有数字数据,但将 跳过 文本字段,而第二遍将执行相反的操作,忽略所有数字数据并仅读取文本字段。 '*'
字符在定义格式说明符时非常有用:
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
,并且您可以在同一个说明符中毫无问题地混合这两种类型(只要它们都是数字)。只需使用对每一列有意义的内容。
有了这些,让我们开始处理文件。数据在 header 行之后的格式化模式中,因此我们首先需要在调用 fscanf
之前通过 header 行。我们会像您一样这样做:
%% 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
的调用允许我们保存文件指针的位置。在我们读取 header 之后,指针正好位于数据段的开头。我们保存它是为了能够 倒回 文件指针指向第二次读取的同一点。
正在读取数值数据:
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
再次注意转置运算符 .'
的使用,以及 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'
显然,如果您选择 table 版本,请使用您从 header 解析的变量名称,而不是我在本例中使用的自动生成的名称。
我真的很想弄清楚如何将包含 9 列和大约 400 行数据的 CSV 数据导入到 Matlab 工作区的 table 中。如果我被允许使用 Matlab 必须提供的 built-in 工具箱,这将很容易,但相反,我需要尝试仅使用 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 跳过文件的 headers 行并将它们添加到它们自己的变量中,但是我不确定从这一点开始创建 table。基本上我想在 Matlab 的工作区中实现 400 行乘 9 列 table。
下面是文本文件的几行示例:
18,8,318,150,3436,11,70,1,sampletext
16,8,304,150,3433,12,70,1,sampletext2
我假设我将不得不使用某些单元格的 built-in 转换函数,这是我被允许的。为了获得正确的帮助,我可能错过了一些重要信息,但是任何人提供的任何帮助都将不胜感激。谢谢。
使用fgetl()
重复检索文本文件行并使用split()
分隔行内容
不确定是否允许在读取文本文件内容后使用 split()
函数,但这里有一个使用 fgetl()
通过循环逐行抓取文本文件内容的实现。在使用 split()
和第二个参数检索所有行后,分隔符设置为逗号 ,
允许将内容拆分为单元格。第一个 for 循环逐行检索文本文件的内容并将其存储在名为 Lines
的字符串中。第二个 for 循环通过分隔符 ,
拆分存储在 Lines
中的字符串,允许将单元格存储在另一个字符串数组中,如下所示,内容分开。这里 -1
表示错误输入 retrieved/when 已到达文件末尾。
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
虽然@MichaelTr7 的回答非常好,但我想建议一个更详细的答案,包括转换为类型并最终返回 table。 请注意,它还包括 预分配 变量。因为 MATLAB 将变量存储在 RAM 中的一致块中,所以最好事先告诉它你的变量有多大。 (MATLAB 实际上抱怨似乎在循环中增长的变量...)
该解决方案也建立在 fgetl
and (later) 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 & 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
读取文件的最低级别函数是, (如 Import Text Data Files with Low-Level I/O 中所述):
fscanf, which reads formatted data in a text or ASCII file; that is, a file you can view in a text editor. For more information, see Reading Data in a Formatted Pattern.
fgetl and fgets, which read one line of a file at a time, where a newline character separates each line. For more information, see Reading Data Line-by-Line.
fread, which reads a stream of data at the byte or bit level. For more information, see Import Binary Data with Low-Level I/O.
在你的情况下,输入文件是 ascii,而不是二进制文件,所以我们可以立即删除最后一个选项 (fread
)。
剩下 fgetl/fgets
(用于逐行读取文件然后解析每一行)和 fscanf
.
你已经有了两个使用逐行方法的答案,所以我不会详细说明这个,而是向你展示如何使用 fscanf
(因为你的数据是 suitable,它们确实以 格式化模式 ).
使用fscanf
的好处是,如果你使用正确的formatSpec
参数,函数将能够一次读取整个文件,而不是逐行迭代。
对于文件中的所有 numeric 数据都是如此。我们将不得不对您最后一列中的文本元素进行第二次传递。
定义格式说明符:
首先让我们定义您的格式规范。我们将为每个通道使用不同的格式。第一遍将读取所有数字数据,但将 跳过 文本字段,而第二遍将执行相反的操作,忽略所有数字数据并仅读取文本字段。 '*'
字符在定义格式说明符时非常有用:
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
,并且您可以在同一个说明符中毫无问题地混合这两种类型(只要它们都是数字)。只需使用对每一列有意义的内容。
有了这些,让我们开始处理文件。数据在 header 行之后的格式化模式中,因此我们首先需要在调用 fscanf
之前通过 header 行。我们会像您一样这样做:
%% 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
的调用允许我们保存文件指针的位置。在我们读取 header 之后,指针正好位于数据段的开头。我们保存它是为了能够 倒回 文件指针指向第二次读取的同一点。
正在读取数值数据:
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
再次注意转置运算符 .'
的使用,以及 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'
显然,如果您选择 table 版本,请使用您从 header 解析的变量名称,而不是我在本例中使用的自动生成的名称。