楼主: meishanjia1900
5567 9

[原创博文] 关于pinseng“请教一个SAS _n_的问题”中set语句的运用规则总结 [推广有奖]

  • 0关注
  • 12粉丝

副教授

44%

还不是VIP/贵宾

-

威望
0
论坛币
3138 个
通用积分
45.8507
学术水平
193 点
热心指数
203 点
信用等级
161 点
经验
25351 点
帖子
703
精华
0
在线时间
988 小时
注册时间
2009-5-17
最后登录
2025-10-12

楼主
meishanjia1900 发表于 2011-11-16 18:26:30 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币
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:

  1. data a;
  2.   input v1;
  3. cards;
  4. 1
  5. 2
  6. 3
  7. 4
  8. 5
  9. ;
  10. data b;
  11.   do until (last);
  12.     set a end=last;
  13.     v2+2;
  14.   end;
  15.   set a;
  16. run;
  17. proc print data=b;
  18. run;
复制代码
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:

  1. data a;
  2.   input v1;
  3. cards;
  4. 1
  5. 2
  6. 3
  7. 4
  8. 5
  9. ;
  10. data b;
  11.   do until (v2>=6);
  12.     v2+1;
  13.     set a;
  14.     put _all_;
  15.   end;
  16. run;
  17. proc print data=b;
  18. run;
复制代码
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:

  1. data a;
  2.   input v1;
  3. cards;
  4. 1
  5. 2
  6. 3
  7. 4
  8. 5
  9. ;
  10. data b(drop=i j);
  11.   do until (v2>2);
  12.     set a; i=1; put i;
  13.     v2+2;
  14.   end;
  15.   set a; j=2; put j;
  16. run;
  17. proc print data=b;
  18. run;
复制代码
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:

  1. data a;
  2.   input v1;
  3. cards;
  4. 50
  5. 60
  6. ;
  7. data b;
  8.   if _n_<=2 then do;
  9.     set a;
  10.     v2=6;
  11.   end;
  12. run;
  13. proc print data=b;
  14. run;
复制代码
OUTPUT:

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

  1. data a;
  2.   input v1;
  3. cards;
  4. 50
  5. 60
  6. ;
  7. data b;
  8.   if _n_<=3 then do;
  9.     set a;
  10.     v2=6;
  11.   end;
  12. run;
  13. proc print data=b;
  14. run;
复制代码
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语句法则的解释,到此结束。
二维码

扫码加我 拉你入群

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

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

关键词:Seng Set PIN eng output 程序

已有 1 人评分学术水平 热心指数 信用等级 收起 理由
ryuuzt + 1 + 1 + 1 佩服精益求精的精神。

总评分: 学术水平 + 1  热心指数 + 1  信用等级 + 1   查看全部评分

沙发
meishanjia1900 发表于 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条记录。




藤椅
ryuuzt 发表于 2011-11-17 09:15:56
你要是不占用这么多层的话,我坐的就是沙发了。

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

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

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

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

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

报纸
cathy3212 发表于 2011-11-23 14:59:21
谢谢楼主啦~收获了

地板
dcyhfut 发表于 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语句。

7
pinseng 发表于 2012-6-18 13:39:06
多谢兄弟。我自己也想总结一下的,但是没有干完。
下个周末仔细拜读一下。

8
zhentao 发表于 2012-6-19 10:12:12
拜读。总结的很好。谢谢。

9
meishanjia1900 发表于 2012-7-28 11:34:05
dcyhfut 发表于 2012-6-18 10:57
请问data b;
  do until (v2>=6);
    v2+2;
凡v2+1, v2+2, sum_x+1等写法出现,其变量(v2, sum_x)均视为保留变量,且视其初始值为0,当然,加过数字后,比如v2+1执行过第一次后就变成v2 = 1了,之后不断累加。

10
zxfrdjjlt 发表于 2014-10-14 11:22:29
今天刚看到,总结的真的很好,再次深刻领悟data步的运行原理,并且也知道了set语句的特点

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
加好友,备注cda
拉您进交流群
GMT+8, 2025-12-31 08:40