3

背景: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 文件的更多详细信息,请参见瑞典统计局网站,但基本要点是元数据位于文件顶部,“DATA=”之后是实际数据本身。还值得注意的是,数据的组织方式更像数据表而不是列。

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

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

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

注意:我知道 R 中的pxR 包 (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 两次提取日期子字符串?也许,有更好(更系统)的方法?

4

2 回答 2

3

通常是解析字符串字段时的方式(如图textscan所示regexp

  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

希望这可以帮助!

于 2013-09-11T20:24:27.933 回答
1

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

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

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

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

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

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

于 2013-09-11T19:56:37.113 回答