【问题标题】:Construct SAS dataset based on file containing metadata基于包含元数据的文件构建 SAS 数据集
【发布时间】:2015-05-18 23:58:16
【问题描述】:

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

包含数据的文件是一个固定宽度的文本文件。也就是说,每一列数据都与文本文件的特定列对齐,并用空格填充以确保对齐。

数据文件.txt:

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

包含元数据的文件以制表符分隔,有两列:一列是数据文件中列的名称,另一列是该列的字符长度。这些名称按照它们在数据文件中出现的顺序列出。

元数据.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;

这让我得到了我需要的东西,但必须有一种更清洁的方式。如果为存储lengthinput 语句的变量分配的空间不足,或者如果语句长度超过最大宏变量长度,则使用这种方法可能会遇到问题。

有什么想法吗?

【问题讨论】:

    标签: sas metadata sas-macro


    【解决方案1】:

    调用执行显示可以提供帮助。

    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 :$8.
          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;
    

    【讨论】:

    • 为什么这比 OP 的例子更干净?如果您认为这是一种改进,请在答案中说明原因,并提供更多信息。
    • @Joe - 我认为问题的最后一部分回答了为什么我的解决方案更干净。实施此解决方案的用户无需担心任何尺寸限制。此外,不会浪费任何数据步,也不会创建浪费的宏变量。我对宏没有任何反对意见,但我喜欢在需要时使用它们。使用基于调用执行的解决方案,用户无需担心数据步变量大小或宏变量大小限制。
    • 然后把它放在你的答案中。您的答案基本上只是代码。只是代码的答案不是好的答案——它们应该解释它们是如何工作的(至少在某种程度上)以及为什么它们应该为手头的问题工作。
    【解决方案2】:

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

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

    首先,一个常见的解决方案是在行级别构建它(就像您所做的那样),然后使用proc sql 创建宏变量。这比数据步法有更大的最大长度限制(如果不使用多个变量,数据步法最大值为$32767,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,或使用文件打开命令直接处理文件。一般来说,这些方法要么比最常见的两种更复杂,要么用处更少,尽管它们当然也是可以接受的解决方案,并且在实践中并不罕见。

    【讨论】:

    • 有没有办法使用proc sql 来完成在input 语句中获取输入列的运行总和?还是应该在数据步骤中提前完成?
    • 你想提前做的。对于这种特殊情况,我可能会使用%include 方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-24
    • 1970-01-01
    • 2018-09-16
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多