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;