基于包含元数据的文件构建SAS数据集

Construct SAS dataset based on file containing metadata

我有两个文本文件,一个包含没有 headers 的原始数据,另一个包含相关的列名和长度。我想使用这两个文件构建一个 SAS 数据集,其中包含一个文件中的数据以及另一个文件中的列名和长度。

包含数据的文件是 fixed-width 文本文件。也就是说,每列数据都与文本文件的特定列对齐,并用 space 填充以确保对齐。

datafile.txt:

John   45   Has two kids
Marge  37   Likes books
Sally  29   Is an astronaut
Bill   60   Drinks coffee

包含元数据的文件 tab-delimited 有两列:一列是数据文件中列的名称,一列是该列的字符长度。名称按照它们在数据文件中出现的顺序列出。

metadata.txt:

Name  7
Age  5
Comments  15

我的目标是拥有一个如下所示的 SAS 数据集:

Name   | Age  | Comments
-------+------+-----------------
John   | 45   | Has two kids
Marge  | 37   | Likes books
Sally  | 29   | Is an astronaut
Bill   | 60   | Drinks coffee

我希望每个列都是具有元数据文件中指定长度的字符。

必须有比我天真的方法更好的方法,即使用导入的元数据构造一个 length 语句和一个 input 语句,如下所示:

/* Import metadata */
data meta;
    length colname $ 50 collen 8;
    infile 'C:\metadata.txt' dsd dlm='09'x;
    input colname $ collen;
run;


/* Construct LENGTH and INPUT statements */
data _null_;
    length lenstmt inptstmt $ 1000;
    retain lenstmt inptstmt '' colstart 1;

    set meta end=eof;

    call catx(' ', lenstmt, colname, '$', collen);

    call catx(' ', inptstmt, cats('@', colstart), colname, '$ &');

    colstart + collen;

    if eof then do;
        call symputx('lenstmt', lenstmt);
        call symputx('inptstmt', inptstmt);
    end;
run;


/* Import data file */
data datafile;
    length &lenstmt;
    infile 'C:\datafile.txt' dsd dlm='09'x;
    input &inptstmt;
run;

这让我得到了我需要的东西,但必须有一种更简洁的方法。如果没有足够的 space 分配给存储 lengthinput 语句的变量,或者如果语句长度超过最大宏变量长度,那么使用这种方法可能 运行 会遇到麻烦.

有什么想法吗?

call execute show 可以提供帮助。

data _null_;
retain start 0;
infile 'c:\metadata.txt' missover end=eof;
    if _n_=1 then do; 
        start=1;
        call execute('data final_output; infile "c:\datafile.txt" truncover; input ');
    end;

input colname :.
      collen  :8.
      ;

call execute( '@'|| put(start,8. -l) || ' ' || colname || ' $'|| put(collen,8. -r) ||'. ' );
start=sum(start,collen);

    if eof then do;
        call execute(';run;');
    end;
run;
proc contents data=final_output;run;

您正在做的是一种相当标准的方法。是的,您可以更仔细地检查一下;例如,为了谨慎起见,我会为这两个语句分配 767

不过,您可以通过一些方法来改善这一点,这可能会消除您的一些担忧。

首先,一个常见的解决方案是在行级别构建它(就像您所做的那样),然后使用 proc sql 创建宏变量。这比数据步长方法有更大的最大长度限制(如果不使用多个变量,数据步长方法最大值为 767,SQL 是 64kib 时的两倍)。

proc sql;
  select catx(' ',colname,'$',collen)
    into :lenstmt separated by ' '
    from meta; *and similar for inputstmt;
quit;

其次,您可以通过写入文件而不是宏变量来超过 64k 的限制。采取你的数据步骤,而不是积累然后使用 call symput,将每一行写到一个 temp 文件(或两个)。然后 %include 这些文件而不是在输入数据步中使用宏变量 - 是的,您可以 %include 在数据步的中间。

还有其他方法,但这两种是最常见的,应该适用于大多数用例。其他一些方法包括 call executerun_macro 或使用文件打开命令直接使用文件。总的来说,它们要么比最常见的两个更复杂,要么用处不大,尽管它们当然也是可以接受的解决方案,并且在实践中并不少见。