SAS中DO循环内SET语句的运行逻辑与结果差异问题
SAS DO循环内SET语句运行逻辑偏差问题
测试基础代码
首先创建测试数据集a1的代码:
/* Create data a1 */ data a1 ; input fruit $ ; cards ; melon apple orange ; run ; proc print data=a1 ; title "Results of a1" ; run;
生成c1的原始代码:
/* Create data c1 using a1 */ data c1 ; do i = 1 to 3 ; set a1 ; count + 1 ; N_VAR = _N_ ; ERR_VAR = _ERROR_ ; output ; end; run ; proc print data=c1 LABEL ; LABEL N_VAR = "_N_" ; LABEL ERR_VAR = "_ERROR_" ; title "Results of c1" ; run ;
实际运行结果:
预期结果的对应代码:
/* 预期c1结果 */ data my_expectation ; input i fruit $ count N ERROR ; cards ; 1 melon 1 1 0 1 apple 2 2 0 1 orange 3 3 0 2 melon 4 1 0 2 apple 5 2 0 2 orange 6 3 0 3 melon 7 1 0 3 apple 8 2 0 3 orange 9 3 0 ; run; proc print data=my_expectation label ; LABEL N = "_N_" ; LABEL ERROR = "_ERROR_" ; title "The result that I expected for c1" ; run ;
核心认知偏差:你没掌握的3个SAS数据步规则
结果和预期不符,本质是对SAS数据步、SET语句的运行逻辑有三个关键误解:
- SET语句的读指针不会自动重置:不带
POINT=选项的SET语句,在整个数据步运行周期内维护一个全局顺序读指针,每执行一次SET,指针向后移动一位读入下一行观测;指针移动到输入数据集末尾之后,下一次执行SET会直接触发整个数据步终止,不会继续执行后续代码。你误以为每次进入DO循环执行SET,都会从a1的第一行重新读取,这个认知是完全错误的。 _N_变量的计数逻辑和输入/输出行数无关:_N_统计的是整个数据步的迭代次数,也就是从DATA语句开头到RUN语句的代码块完整执行的次数,从1开始逐次累加。DO循环是数据步内部的子循环,不会触发_N_的增长,_N_也不会因为换了读入的数据集、读入新观测就重置。- 数据步不会自动在内部循环边界重置变量:除了SET语句读入的变量会在新观测读入时覆盖旧值,累加语句(比如
count+1)、手动赋值的变量都会在整个数据步运行周期内保留值,不会自动重置。
原始代码的实际运行流程
按照SAS的真实运行逻辑,你写的c1生成代码只会输出3条观测,流程如下:
- 数据步启动,第一次迭代开始,此时
_N_=1,count初始值为0,a1的读指针指向数据集开头 - 进入
do i=1 to 3循环:- i=1:第一次执行
set a1,读指针移动到a1第1行,读入fruit=melon;count累加为1;N_VAR赋值为当前_N_值1;ERR_VAR为0;执行output输出第一行观测 - i=2:第二次执行
set a1,读指针移动到a1第2行,读入fruit=apple;count累加为2;N_VAR还是1;输出第二行观测 - i=3:第三次执行
set a1,读指针移动到a1第3行,读入fruit=orange;count累加为3;N_VAR还是1;输出第三行观测
- i=1:第一次执行
- i=1到3的DO循环执行完毕,回到数据步开头准备第二次迭代,此时
_N_累加到2 - 第二次迭代启动,再次进入
do i=1 to 3循环,i=1时执行set a1,此时读指针已经在a1数据集末尾,读不到任何观测,直接触发数据步终止,后续代码全部不执行
最终c1数据集就只有3条观测,和你截图的结果完全一致。
如何得到你预期的9行结果
要实现你预期的“i从1到3,每次i循环都完整读入a1的3行观测”的效果,必须用POINT=选项手动指定SET语句读取的行号,强制绕开顺序读指针的限制,代码如下:
data c1_expect ; do i = 1 to 3 ; do p = 1 to nobs; /* p指定要读取的a1行号 */ set a1 point=p nobs=nobs; /* nobs选项会自动读取a1总观测数 */ count + 1 ; /* 你预期的N是a1内部的行号,不是SAS自带的_N_,直接用p赋值即可 */ N_VAR = p ; ERR_VAR = _ERROR_ ; output ; end; end; stop; /* 用POINT=读入数据不会自动触发数据步终止,必须手动加STOP防止死循环 */ run ;
注意:你预期中每次读a1观测时N值从1到3循环,本质是要取a1的内部行号,这个值和SAS自带的_N_没有任何关系,直接用POINT选项的行号变量赋值即可。
内容的提问来源于stack exchange,提问作者Seo Gitae
相关产品推荐
相关产品推荐

