【问题标题】:Parse a PC-Axis (.px) file in Matlab在 Matlab 中解析 PC 轴 (.px) 文件
【发布时间】:2013-09-15 23:12:56
【问题描述】:

背景:PC-Axis 是一种用于传播统计信息的文件格式。许多国家统计机构使用该格式来发布官方统计数据。

PC-Axis 文件看起来有点像这样,尽管它们通常要长得多:

CHARSET=”ANSI”;
MATRIX="BE001";
SUBJECT-CODE="BE";
SUBJECT-AREA="Population";
TITLE="Population by region, time, marital status and sex.";
Data=
".." ".." ".." ".." ".." 
".." ".." ".." ".." ".." 
".." 24.80 34.20 52.00 23.00 
".." 32.10 40.30 50.70 1.00 
".." 31.60 35.00 49.10 2.30 
41.20 43.00 50.80 60.10 0.00 
50.90 52.00 53.90 65.90 0.00 
28.90 31.80 39.60 51.00 0.00;

有关 PC-Axis 文件的更多详细信息可以在 Statistics Sweden website 找到,但基本要点是元数据位于文件顶部,“DATA=”之后是实际数据本身。还值得注意的是,数据的组织方式更像数据表而不是列。

问题:我想用 Matlab 解析一个 PC-Axis 文件,但我有点不知道如何去做。有谁知道如何在 Matlab 中解析这些文件之一?使用其他语言(如 Perl)解析这种类型的文件,然后将数据导入 Matlab 会更容易吗,或者,Matlab 是否适合这项工作?请注意,计划是在文本处理阶段之后在 Matlab 中分析数据。

我尝试过使用 Matlab 的文本处理工具,例如 fgetl、textscan、fscanf 和其他一些工具,但是非常棘手。有没有人对如何去做有任何指示?

基本上,我想将每个关键字(CHARSET、MATRIX 等)及其对应的值(ANSI、BE001 等)作为元数据存储在 Matlab 中——也许是一个结构。我也想将数据存储在 Matlab 中 - 例如,作为矩阵。

注意:我知道 R 中的 pxR package (CRAN),它可以将 .px 文件作为 data.frame 对象读入工作区。还有一个名为Data::PcAxis (CPAN) 的 Perl 模块也非常好,但我特别想知道如何使用 Matlab 解析 .px 文件。

更新:我应该提到除了元数据数据,还有变量。这最好用一个例子来解释。下面的示例 PC-Axis 文件与上面的相同,只是我添加了两个变量。它们被命名为 VALUES("Month") 和 VALUES("region"),位于元数据之后和数据之前

CHARSET=”ANSI”;
MATRIX="BE001";
SUBJECT-CODE="BE";
SUBJECT-AREA="Population";
TITLE="Population by region, time, marital status and sex.";
VALUES("Month")="1976M01","1976M02","1976M03","1976M04",
"1976M05","1976M06","1976M07","1976M08",
"1976M09","1976M10","1976M11","1976M12";
VALUES("region")="Sweden","Germany","France",
"Ireland","Finland";
Data=
".." ".." ".." ".." ".." 
".." ".." ".." ".." ".." 
".." 24.80 34.20 52.00 23.00 
".." 32.10 40.30 50.70 1.00 
".." 31.60 35.00 49.10 2.30 
41.20 43.00 50.80 60.10 0.00 
50.90 52.00 53.90 65.90 0.00 
28.90 31.80 39.60 51.00 0.00;

Textscan 在将文本文件的每一行作为字符串(在元胞数组中)读取时会起到很好的作用。但是,两个变量(即 VALUES("Month") 和 VALUES("region"))的“=”符号后面的元素跨越多行。似乎在这种情况下使用 textscan 意味着必须连接一些字符串,例如,为了收集月份列表(1976M01 到 1976M12)。

问题:收集变量数据的最佳方法是什么?将文本文件作为单个字符串读取,然后使用 strtok 两次提取日期子字符串?也许,有更好(更系统)的方法?

【问题讨论】:

  • 我很确定 Matlab 足以满足您的需求。我会试一试,然后发布您的具体挑战。例如,尝试使用 textscan 解析矩阵(请参阅下面的答案)。如果您遇到麻烦,请发布您的特定文本扫描问题。祝你好运!

标签: matlab text-parsing


【解决方案1】:

我个人对 PC-Axis 文件并不熟悉,但这是我的想法。

首先解析标题。如果标题是固定大小的,您可以读入那么多行并解析出您想要的值。 regexp 方法可能对此有用。

数据看起来既是字符串又是数字。我会将“..”值更改为 NaN(当然,首先进行原始备份),然后使用 textscan 扫描矩阵。 Textscan 可能很棘手,因此请确保文件完全解析。如果 textscan 遇到与格式字符串不匹配的行,它将停止解析。你可以检查文件句柄的位置(使用ftell)看它是否匹配文件末尾(你可以fseek到文件末尾找到什么该值应该是)。 textscan 返回的元胞数组的长度应该都相同。如果没有,长度会告诉你他们在哪一行失败——你可以用文本编辑器检查这一行,看看是什么违反了格式。

您可以使用字符串参数分配和访问 Matlab 结构中的字段。例如:

foo.('a') = 1;
foo.a
ans = 
     1

所以,我建议的工作流程是解析标题行,将每个属性/值对分配为结构中的字段/值对。然后解析矩阵(经过一些简短的文本预处理以确保所有数据都是数字)。

【讨论】:

  • 很好的答案,戈登比恩。谢谢您的帮助。我想问你是否知道解析我在对我的问题所做的更新中提到的“变量”。非常感谢您提供的任何建议。
【解决方案2】:

通常textscanregexp是解析字符串字段时要走的路(如图here):

  1. 使用textscan将输入行读取为字符串:

    fid = fopen('input.px', 'r');
    C = textscan(fid, '%s', 'Delimiter', '\n');
    fclose(fid);
    
  2. 使用regexp 解析标头字段名称和值。选择正确的正则表达式应该可以解决问题!

    X = regexp(C{:}, '^\s*([^=\(\)]+)\s*=\s*"([^"]+)"\s*', 'tokens');
    X = [X{:}];                          %// Flatten the cell array
    X = reshape([X{:}], 2, []);          %// Reshape into name-value pairs
    
  3. “VALUE”字段可能跨越多行,因此需要先将它们连接起来:

    idx_data = find(~cellfun('isempty', regexp(C{:}, '^\s*Data')), 1);
    idx_values = find(~cellfun('isempty', regexp(C{:}, '^\s*VALUES')));
    Y = arrayfun(@(m, n){[C{:}{m:m + n - 1}]}, ...
       idx_values(idx_values < idx_data), diff([idx_values; idx_data]));
    

    ...然后标记化:

    Y = regexp(Y, '"([^,"]+)"', 'tokens');  %// Tokenize values
    Y = cellfun(@(x){{x{1}{1}, {[x{2:end}]}}}, Y); %// Group values in one array
    Y = reshape([Y{:}], 2, []);             %// Reshape into name-value pairs
    
  4. 确保字段名称合法(我决定将所有内容都转换为小写,并将撇号和任何空格替换为下划线),并将它们插入到结构中:

    X = [X, Y];                             %// Store all fields in one array
    X(1, :) = lower(regexprep(X(1, :), '-+|\s+', '_')); 
    S = struct(X{:});
    

这是我从您的输入文件中得到的(只有标题字段):

S =
          charset: 'ANSI'
           matrix: 'BE001'
     subject_code: 'BE'
     subject_area: 'Population'
            title: 'Population by region, time, marital status and sex.'
            month: {1x12 cell}
           region: {1x5 cell}

至于数据本身,需要单独处理:

  1. 提取“数据”字段后的数据行并将所有 ".." 值替换为默认值(例如,NaN):

    D = strrep(C{:}(idx_data + 1:end), '".."', 'NaN');
    

    显然,这假设“数据”字段之后只有数字数据。但是,如果不是这种情况,可以轻松修改。

  2. 将数据转换为数值矩阵并添加到结构中:

    D = cellfun(@str2num, D, 'UniformOutput', false);
    S.data = vertcat(D{:})
    

这里是您的输入文件的S.data

S.data =

        NaN        NaN        NaN        NaN        NaN
        NaN        NaN        NaN        NaN        NaN
        NaN   24.80000   34.20000   52.00000   23.00000
        NaN   32.10000   40.30000   50.70000    1.00000
        NaN   31.60000   35.00000   49.10000    2.30000
   41.20000   43.00000   50.80000   60.10000    0.00000
   50.90000   52.00000   53.90000   65.90000    0.00000

希望这会有所帮助!

【讨论】:

  • 优秀的答案,Eitan T!非常感谢您的帮助。我已经能够修改您建议的代码以处理我在原始问题中未提及的其他一些事情。我想知道您是否可以就我对该问题所做的更新提供第二轮帮助。如果你能看一看,那就太好了。非常感谢您的帮助。
  • @GraemeWalsh 很高兴为您提供帮助。您希望如何解释“值”字段?也就是生成的结构体中对应的字段应该是怎样的?
  • 感谢您回复我,Eitan。也许像下面这样。 S.month = 1976M01 1976M02 1976M03 1976M04 等等......这样输出是一个 n x 1 列向量/矩阵。对于 S.region =,输出可以是 n x 1 和 1 x n 向量/矩阵。
  • @GraemeWalsh 是的,我在打字时忘记了Y = cellfun(...) 行中的一对花括号。我认为它现在应该可以工作了。
  • 代码现在可以工作了。非常感谢@EitanT 的帮助。我希望我能不止一次地支持你的答案!很好的答案,再次感谢。
猜你喜欢
  • 2013-03-21
  • 2014-05-28
  • 2012-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多