2.5 理解SAS运行机制

SAS的学习曲线比较陡峭,其原因之一就是很多SAS学习者没有深入理解SAS的运行机制,其中最为重要的机制就是PDV(Program Data Vector)与DATA步自循环。

→2.5.1 PDV与DATA步自循环

很多时候,即使是写了很多SAS程序、用了很长时间SAS的人,也总是会对SAS DATA步运行出的结果感到莫名其妙,对发生的错误更是一头雾水,但是如果能够静下心来,了解PDV、厘清SAS的运行机制,很多疑惑或许就迎刃而解了。

SAS系统处理SAS DATA步时,分两步:编译和执行。经典的DATA步,基本按照图2-14的流程来。

图2-14 DATA步动作流程图

具体而言,在编译和执行阶段,SAS会分别进行如表2-6所示的操作。

表2-6 编译和执行阶段具体动作

在上面的过程中,有两个概念不是很好理解:一是输入缓冲区(Input Buffer);二是程序数据向量(Program Data Vector)。这两个概念都是内存里的一个逻辑区域,我们简要示图如图2-15所示。

图2-15 Input Buffery与PDV

Buffers是系统内存的缓冲区,我们可以先不细究。如图2-15所示,分别展示了读入原始数据和读入SAS数据集时的流程。

(1)读入原始数据时:原始数据先读入Input Buffer,再从Input Buffer转换到PDV,最后从PDV输出到SAS数据集。

(2)读SAS数据时:把数据集观测直接读入到PDV,再从PDV输出到数据集。

我们再次以一个小程序为例,看看Input Buffer与PDV,了解SAS DATA步的运行机制。

程序2-21 PDV演示程序

    datademoPDV;
        input ID $   Chinese   Math   English;
        Sum=Chinese+Math+English;
    datalines;
    S001 80 99 93
    S002 90 85 95
    S003 83 88 81
    ;
    run;

在编译阶段,SAS就知道这个要建立的数据集叫DemoPDV,有ID、Chinese、Math、English以及Sum五个变量,其中ID为字符型。SAS给它们建立好Input Buffer和PDV。

Input Buffer:内存里开辟空间,以便中转数据。

PDV:从Input语句或者SET、MERGE、UPDATE语句获取变量信息,建立好数据变量。

运行阶段:

(1)设置INPUT中的变量为缺失(字符变量为空白,数字变量为小数点),并设置自动变量_N_=1,_ERROR_=0;

(2)INPUT语句读入第一条记录,Input Buffer和PDV的状态。方框可以理解为在运行的程序部分;

开始INPUT语句:

    datademoPDV;
        input ID $   Chinese   Math   English;
        Sum=Chinese+Math+English;
    datalines;
    S001 80 99 93
    S002 90 85 95
    S003 83 88 81
    ;
    run;

读入第一个变量ID。

    datademoPDV;
        input ID $   Chinese   Math   English;
        Sum=Chinese+Math+English;
    datalines;
    S001 80 99 93
    S002 90 85 95
    S003 83 88 81
    ;
    run;

读入第二个变量。

    datademoPDV;
        input ID $   Chinese   Math   English;
        Sum=Chinese+Math+English;
    datalines;
    S001 80 99 93
    S002 90 85 95
    S003 83 88 81
    ;
    run;

如此直到最后一个变量sum。

    datademoPDV;
        input ID $   Chinese   Math   English;
        Sum=Chinese+Math+English;
    datalines;
    S001 80 99 93
    S002 90 85 95
    S003 83 88 81
    ;
    run;

(3)完成所有DATA步后续语句,SAS自动完成输出数据集。

    datademoPDV;
        input ID $   Chinese   Math   English;
        Sum=Chinese+Math+English;
    datalines;
    S001 80 99 93
    S002 90 85 95
    S003 83 88 81
    ;
    run;

将上面PDV里除了自动变量_ERROR_,_N_外,其他变量自动输出到数据集DemoDPV。

(4)返回DATA步第一语句,初始化PDV。

    datademoPDV;
        input ID $   Chinese   Math   English;
        Sum=Chinese+Math+English;
    datalines;
    S001 80 99 93
    S002 90 85 95
    S003 83 88 81
    ;
    run;

(5)开始读入第二条记录的第一个变量ID。

(6)如此循环重复,读完最后一条记录的最后一个变量,写入数据集。

(7)再次返回第一条DATA语句,发现已经没有数据可以读取,直到这时,DATA步才彻底结束。

如何粗略的验证上述步骤呢?我们可以尝试运行程序2-22验证PDV,看LOG窗口给我们的信息提示。

程序2-22 验证PDV

    datademoPDV;
        put "第" _n_ "次运行前:" _all_;
        input ID $   Chinese   Math   English;
              Sum=Chinese+Math+English;
        put  "第" _n_ "次运行后:" _all_;
    datalines;
    S001   80   99   93
    S002   90   85   95
    S003   83   88   81
    ;
    run;

LOG的结果显示:

    第1 次运行前:ID=  Chinese=. Math=. English=. Sum=. _ERROR_=0 _N_=1
    第1 次运行后:ID=S001 Chinese=80 Math=99 English=93 Sum=272 _ERROR_=0 _N_=1
    第2 次运行前:ID=  Chinese=. Math=. English=. Sum=. _ERROR_=0 _N_=2
    第2 次运行后:ID=S002 Chinese=90 Math=85 English=95 Sum=270 _ERROR_=0 _N_=2
    第3 次运行前:ID=  Chinese=. Math=. English=. Sum=. _ERROR_=0 _N_=3
    第3 次运行后:ID=S003 Chinese=83 Math=88 English=81 Sum=252 _ERROR_=0 _N_=3
    第4 次运行前:ID=  Chinese=. Math=. English=. Sum=. _ERROR_=0 _N_=4

最后补充说明一下:上面所展示的都是SAS默认的、最基础的、最简单的运行机制。当DATA步有循环、选择语句,有OUTPUT、RETAIN等语句时,SAS的处理流程会有所不同。

→2.5.2 @与@@的困惑

初学SAS者,或多或少都会对@与@@的理解有些吃力。官方对@的说法是:INPUT语句尾部的@是行保持符,主要作用是保持数据行停留在此行,不要跳到下一行。

@称为单尾@,@@称为双尾@,很多情况下,我们连一个@也不用,我姑且称之为无尾。那么什么情况下用无尾、什么情况下用单尾、什么情况下用双尾呢?以下是笔者总结的一些原则:

● 当DATALINES数据行里要读入的数据列数=要读入的变量数,也就是说一行就是一条观测时,无尾。

● 当DATALINES数据行里要读入的数据列数>要读入的变量数,而且是整数倍时,也就是说一行= K*数条观测(K为≥1的整数),用@@。

● 当一个DATA步里有多个INPUT语句时,我们需要单尾@。

程序2-23 @与@@示例程序

    *=== 数据列数=变量数;
    datatest1;
          input id x y z;
          datalines;
          1 98 99 97
          2 93 91 92
          ;
    run;
    *=== 数据列数=变量数,多个input语句;
    datatest2;
          input id@;
          input x@;
          input y@;
          input z@;
          datalines;
          1 98 99 97
          2 93 91 92
          ;
    run;
    *=== 数据列数=k*变量数;
      datatest3;
          input id x y z @@;
          datalines;
          1 98 99 97 2   93 91 92
          ;
      run;

关于@、@@与跳行,笔者曾简单总结了如下原则:

● 无尾Hold不住立即跳。

● 一尾(@)Hold当前INPUT语句不跳,但若刚好是DATA步最后一个INPUT语句,跳。

● 二尾(@@)打死都不跳。

● 最后,无论多少尾,数据行末尾必定自动跳。

例如,实例程序2-24 @与@@的辨析中第一个程序,由于INPUT X后面有@,且不是最后一个INPUT语句,故读完X=1后,不跳行,继续读Y=2, 由于INPUT Y后无尾,立即跳行,故读Z时为Z=4,又因INPUT Z后有@@,虽然这是最后一个DATA步的IPUT语句,不跳,程序返回开头,开始读第二条观测,X=5,不跳,Y=6,跳,Z=7。故最终的结果为两条观测,X,Y,Z的值分别为:1,2,4;5,6,7。第二个程序,答案是1,4,5,各位读者能运用上面的原则得出答案吗?

程序2-24 @与@@的辨析

    datatest;
        input x @;          /*单个@,能Hold住,读后不跳*/
        input y;            /*没有@,Hold不住,读后跳*/
        input z @@;         /*两个@,Hold住没问题,但读入y=6后,到达数据行末尾,因此自动跳*/
    datalines;
    1 2 3
    4 5 6
    7
    ;
    run;


    data test;
        input x ;           /*无@,Hold不住,读后立即跳*/
        input y @@;         /*两个@,Hold住,读后不跳*/
        input z @;          /*单个@,但是是最后一个INPUT语句,跳*/
    datalines;
    1 2 3
    4 5 6
    7;
    run;