全部版块 我的主页
论坛 数据科学与人工智能 数据分析与数据科学 SAS专版
5525 9
2011-11-16
pinseng发了名为《请教一个SAS _n_的问题》的帖子。

他想问以下程序中if _n_=1的意义是什么:

data temp;
input x y@;
datalines;
1 2
3 4
5 6
. 9
6 7
7 .
1 8
6 3
;
run;
data q3;
    if _n_=1 then do until (last);
        set temp nobs=obs end=last;
        sum_x+x;
        sum_y+y;
    end;
    set temp;
    if x=. then x=sum_x/obs;
    if y=. then y=sum_y/obs;
run;

源自:https://bbs.pinggu.org/thread-1246757-1-1.html

---------------------------------------------------------------------------------------

问题本身已经解决了,这里只是想总结一下我在参与回复的过程中所探索出的一些东西——“set语句的法则”,它是我尝试过很多小程序后的一个结论。

仔细想想还是有些复杂的:

***************************************************

set语句的法则——当DATA b;数据步中出现多个set a;语句时,遵循如下法则:

(1)计算机对程序中每一处的set a;语句分别设置各自对a集的读取进程,每个set a;语句第一次执行时均从a集的第一条记录开始读入,之后,分别按各自被执行的次数一条一条读取a的记录,注意,DATA步的大循环不会打断这种读取进程的推进,每个特定位置的set语句,仍可在新循环里沿着自己上一次DATA步循环时的a集读取进程继续往下读。

(2)同一DATA b;循环中,每次只针对该循环对应的特定b记录,多次写入set a;语句读出的a记录数据。且对于b的该特定记录行从a处读入的变量,写入的新数据会覆盖旧数据。


(3)DATA步的上一次循环中执行的最后一处set a;为b集读入的a集变量值也将在该次循环开始时被再次写入到这个b集的新的记录行里,形成与上一行b记录重复的格局。即,set a;创建的变量在b集中被当作保留变量。但是要注意,新记录也一如既往的没有禁止覆盖!如果新循环中执行了set,则默认值将被覆盖。若新的DATA循环中没有set语句被执行,且保留变量没被其他语句修改,那么b的新记录行保留默认值。

(4)只要DATA新循环中没有任何set语句被执行,那在完成该DATA循环后,程序自动终止。

(5)无论程序中哪一处的set a;语句在先行读到a的最后一条记录的进度基础上不知“适可而止”,仍被DATA b;程序再次执行,导致“超出极限读取”的,该data b;循环将不产生(理解为抹消也可以)对应的整条b集记录,且data b;数据步终止。

***************************************************

解释:

-------------------------------------------------------------

例1:

复制代码
OUTPUT:

                                          Obs    v1    v2
                                            1       1     10

data b;程序段中出现了两处set a;语句。计算机将分别设置它们各自的读取计划。具体情况如以下流程:

&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

( i  )b的循环次数(即对应的b记录行数)-------》第1次(针对b的第1条记录):

(a)先看do循环,do之内的set被执行了5次,v2+2也执行了5次:
          do第1次循环:v1=1      do第1次循环:v2=2        ------------》  第1行记录:v1=1,v2=2
          do第2次循环:v1=2      do第2次循环:v2=4        ------------》  第1行记录被覆盖为:v1=2,v2=4
          do第3次循环:v1=3      do第3次循环:v2=6        ------------》  第1行记录被覆盖为:v1=3,v2=6
          do第4次循环:v1=4      do第4次循环:v2=8        ------------》  第1行记录被覆盖为:v1=4,v2=8
          do第5次循环:v1=5      do第5次循环:v2=10      -------- ---》  第1行记录被覆盖为:v1=5,v2=10
(b)do循环之后,执行下一句——set a; 该处的set还是第一次执行:
          初次执行该语句:v1=1   ------------------------------------------》  第1行记录被覆盖为:v1=1,v2=10(v2未遭到覆盖!)

(i i )b的循环次数(即对应的b记录行数)-------》第2次(针对b的第2条记录):

   开头时,在b集第2条记录里,默认认为v1=1。这体现了法则(3)的内容。

   之后,程序碰到do循环,虽然last值仍保留成数字1,表示do中的set语句已经触及a的底部,但是do语句每次开始第1次循环时,都不对判断条件加以把关。故虽然until (last)被满足,但do还是被执行了。

   do中的set a;明明已经读取到了a的底部,但仍然被执行了,造成“超限读取”,依据法则(5),程序到此终止,且抹消掉b的第2条记录。

   故DATA b;循环了2次,但b集只产生了1条记录。

&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

对输出结果的解释到此完毕。

-------------------------------------------------------------

例2:

复制代码
OUTPUT:

                   空,无输出内容!表明b集无数据!

LOG:

v2=1 v1=1 _ERROR_=0 _N_=1
v2=2 v1=2 _ERROR_=0 _N_=1
v2=3 v1=3 _ERROR_=0 _N_=1
v2=4 v1=4 _ERROR_=0 _N_=1
v2=5 v1=5 _ERROR_=0 _N_=1

LOG中的输出表明b集的第1次循环确实执行过,但为什么没有形成第1条记录呢?——“超限读取”!法则(5)表明b集不可能有记录。就算前面演算得再精彩,黑板擦一擦,什么都没了。

-------------------------------------------------------------

一个更难的例子:

例3:

复制代码
OUTPUT:

                                          Obs    v2    v1
                                            1       4      1
                                            2       6      2
                                            3       8      3
                                            4     10      4

LOG:

1
1
2
1
2
1
2
1
2

这个例子我不详细说了,但这里我提一些问题,以启发各位的思考:

(A)为什么LOG栏的输出结果中,在第1个数字2出现之后,还会间隔着出现数字1呢?提示:do until (...)开始第一次循环时是不对条件(...)把关的!

(B)为什么记录1中v1=1,而v2却是4?请结合法则中的(2)思考。

(C)为什么记录只有4行,而不是5行?请结合法则中的(2)与(5)思考。

(D)data b;总共循环了几次?请结合法则(5)思考。

这更像是一个练习,且难度较大。

-------------------------------------------------------------

更多的例子:

例4:

复制代码
OUTPUT:

                                          Obs    v1    v2
                                            1      50     6
                                            2      60     6
                                            3      60     .

复制代码
OUTPUT:

                                          Obs    v1    v2
                                            1      50     6
                                            2      60     6

为什么第一段程序的结果与第二段程序的结果不同呢?

两段程序的DATA b;都循环了3次,第一段程序在第3次循环中没有执行任何set语句,故第3行的b记录中v1的值默认与上一行相同。这主要体现了法则(3)、(4)的内容。

第二段程序在第3次循环中又执行了一次set a;语句,a只有2行记录,但该处的set前后共运行了3次,是“超限读取”,因此不形成第3条记录,且程序终止。

-------------------------------------------------------------

例1、例2、例3、例4都弄懂之后,对set语句法则的理解应该就没有问题了。

对set语句法则的解释,到此结束。
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

全部回复
2011-11-16 20:29:43
在pinseng的帖《请教一个SAS _n_的问题》中,我曾经抱怨SAS的if_n_then...set a;...句式有BUG。

后来编了很多小程序做试验,才逐渐摸索出set语句的运行法则。

现在,我并不认为SAS在这个问题上存在BUG——至少它是按照一定的规则行事的。

虽然不同的程序可能会得到各种“坑爹”的结果,但SAS自身的操作法则却很精简,只有5条。

至少它并不凌乱,没有想到哪里就是哪里。

现在,就让我们用这套“set语句运行法则”去详细分析pinseng给出的程序。

data q3;
    if _n_=1 then do until (last);
        set temp nobs=obs end=last;
        sum_x+x;
        sum_y+y;
    end;
    set temp;
    if x=. then x=sum_x/obs;
    if y=. then y=sum_y/obs;
run;

首先是data q3; 依据法则(1):

“(1)计算机对程序中每一处的set a;语句分别设置各自对a集的读取进程,每个set a;语句第一次执行时均从a集的第一条记录开始读入,之后,分别按各自被执行的次数一条一条读取a的记录,注意,DATA步的大循环不会打断这种读取进程的推进,每个特定位置的set语句,仍可在新循环里沿着自己上一次DATA步循环时的a集读取进程继续往下读。”

我们可以将data q3;中的set temp划分为两处,一个在do循环中,一个在do循环外。每个set语句按照自己独立的进度读取数据集temp。

data q3;第1次大循环时,即_n_=1时,自然激活do这个小循环,do中的set temp;语句被执行了8次,小循环完成时,此处的set temp;语句恰好按照自己的进度取尽了数据集temp。请注意,不是说这8次读取的x与y值分成8行写入q3集,我们看法则(2):

“(2)同一DATA b;循环中,每次只针对该循环对应的特定b记录,多次写入set a;语句读出的a记录数据。且对于b的该特定记录行从a处读入的变量,写入的新数据会覆盖旧数据。”

由于整个处在data q3;的第1次大循环中,即_n_=1,故这8次读取的x、y值都写在q3集的第1条记录里,后者覆盖前者。

do循环第1次,set读取temp的第1条记录:x=1,y=2,q3集同时算出sum_x=1,sum_y=2。
此时q3记录为:
Obs  x  y  sum_x  sum_y
  1    1  2     1          2

do循环第2次,set读第2条记录:x=3,y=4,,q3集同时算出sum_x=1+3=4,sum_y=2+4=6。
新记录覆盖旧记录。
此时q3记录为:
Obs  x  y  sum_x  sum_y
  1    3  4     4          6

......

如此循环,直到第8次结束。

此时,q3集被覆盖成:
Obs  x  y  sum_x  sum_y
  1    6  3     29         39

do循环结束后,开始执行后面的语句——另一处的set temp;

按法则(1),此处的set temp;有自己的进程,与do里面的set temp;没有半点联系。这是第一次执行do循环以外的set语句。故该语句从temp集的第1条记录开始读:x=1,y=2。

故原来的x=6,y=3被覆盖,q3集变成:

Obs  x  y  sum_x  sum_y
  1    1  2     29         39

之后是执行两个判断语句:

    if x=. then x=sum_x/obs;
    if y=. then y=sum_y/obs;

随后data q3;的第1次大循环顺利结束。

之后,data q3;开始第2次大循环。

依据法则(3):

“(3)DATA步的上一次循环中执行的最后一处set a;为b集读入的a集变量值也将在该次循环开始时被再次写入到这个b集的新的记录行里,形成与上一行b记录重复的格局。即,set a;创建的变量在b集中被当作保留变量。但是要注意,新记录也一如既往的没有禁止覆盖!如果新循环中执行了set,则默认值将被覆盖。若新的DATA循环中没有set语句被执行,且保留变量没被其他语句修改,那么b的新记录行保留默认值。”

在第2次循环开始时,q3集第2条记录中,默认值为:x=1,y=2

程序里的set语句有两处:do里的set和do外的set。

此时_n_=2,所以,if _n_=1 then后的语句群不会再被激活,否则do里面的set语句就成了“超限读取”,q3集的第2条记录就创建失败了!但此时不涉及该问题。

第2次大循环里,程序直接从do之外的set temp;开始运行。按照法则(1),这个set依自己的进程读取temp,在上一次data大循环里,该处的set执行了1次,这次是第2次,故此处的set temp;会读取temp的第2条记录!于是,q3集第2条记录x与y值被覆盖,为:x=3,y=4。

总体呈现:

Obs  x  y  sum_x  sum_y
  1    1  2     29         39
  2    3  4      ?         ?

现在问题是q3第2条记录里的sum_x与sum_y应该为多少呢?

程序没交代。

如果是类似于普通变量,比如y=1,x=z+3之类的变量,一旦某条记录里没有定义,那么该记录里的变量值就只能设为空,即:

x=.   y=.

但是,一旦采用sum_x+1,sum_y+1这类的写法,那么变量sum_x,sum_y就视为保留变量,与"retain 变量名"语句定义的保留变量具有同等效力。

保留变量的值,你不动它,它就保留原值,你动它,它就改变,很方便。

保留变量是编写DATA步的重要工具之一。

故此处,在q3的记录2里,我们也有sum_x=29,sum_y=39。

故q3集为:

Obs  x  y  sum_x  sum_y
  1    1  2     29         39
  2    3  4     29         39

之后再执行那两个if语句,对于第2条记录,这也改变不了什么。

之后,data q3;的第2次大循环结束。

没有停止循环事由,于是,data q3;开始第3次大循环。

如此,直到第8次循环:

---------------------------------------------------------------------------------------------

OUTPUT:

                              Obs       x         y       sum_x    sum_y
                               1     1.000    2.000       29        39
                               2     3.000    4.000       29        39
                               3     5.000    6.000       29        39
                               4     3.625    9.000       29        39
                               5     6.000    7.000       29        39
                               6     7.000    4.875       29        39
                               7     1.000    8.000       29        39
                               8     6.000    3.000       29        39


---------------------------------------------------------------------------------------------

data q3;还会执行第9次循环。

循环开始时的变量默认值为:x=6,y=3,sum_x=29,sum_y=39

但是,第9次循环会执行do之外的那个set temp;语句。

此时,它已被执行第9次了,而temp只有8条记录。

故形成“超限读取”,依据法则(5):

“(5)无论程序中哪一处的set a;语句在先行读到a的最后一条记录的进度基础上不知“适可而止”,仍被DATA b;程序再次执行,导致“超出极限读取”的,该data b;循环将不产生(理解为抹消也可以)对应的整条b集记录,且data b;数据步终止。”

故程序终止,且不产生q3的第9条记录。




二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

2011-11-17 09:15:56
你要是不占用这么多层的话,我坐的就是沙发了。
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

2011-11-17 20:28:13
该帖今天再次修改过一次,将“set语句运行规则”精简到了5点。简洁即正确,我想应该没错了。

我并非是从教科书上摘抄的,这一规则是我自己试出来的,所以并不能保证正确。

但至少该规则可以在能够解释以上程序的基础上做到最精简。

我相信它就是站在纷繁复杂的程序与结果之后的真实规律。

有时我真的感觉做这项工作就像是做侦探,需要很强的推理能力——从众多的表象推出本质。
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

2011-11-23 14:59:21
谢谢楼主啦~收获了
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

2012-6-18 10:57:37
请问data b;
  do until (v2>=6);
    v2+2;
    set a;
    put _all_;
  end;
run;
log:
v2=2 v1=1 _ERROR_=0 _N_=1
v2=4 v1=2 _ERROR_=0 _N_=1
v2=6 v1=3 _ERROR_=0 _N_=1
v2=8 v1=4 _ERROR_=0 _N_=2  /*重新data步为什么没有重新初始化为0,难道有隐含retain????????????*/
v2=10 v1=5 _ERROR_=0 _N_=3
数据集还是用你的上面的a,就是当进行第二次data步循环时,不是除了retain变量之外,其余的变量均要初始化吗?那v2=8又是怎么回事呢,求解?,还有一个就是哪些语句包含隐含的retain语句呢,我只知道双set语句有隐含retain语句。
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

点击查看更多内容…
相关推荐
栏目导航
热门文章
推荐文章

说点什么

分享

扫码加好友,拉您进群
各岗位、行业、专业交流群