本节书摘来自华章出版社《深入解析sas:数据处理、分析优化与商业应用》一书中的第3章,第3.2节,作者 夏坤庄 徐唯 潘红莲 林建伟,更多章节内容可以访问云栖社区“华章计算机”公众号查看
3.2 操作数据集的观测
在对数据集进行操作时,通常会需要对满足条件的特定观测进行操作,例如修改变量值等,这时需要用到条件语句、赋值语句,以及表达式。本节首先介绍SAS表达式,接着讲解如何使用表达式结合条件语句和赋值语句来对SAS数据集的观测进行操作,最后介绍使用SORT过程对数据集排序,以实现对观测进行分组。此外,还简单地介绍如何对单个分组数据进行操作。分组数据在对多个SAS数据集进行加工时会有更为丰富的使用,这部分内容在第4章中进行介绍。
3.2.1 SAS表达式
表达式是操作数和操作符的序列,该序列会形成一组可执行并产生结果值的指令。其中,操作数可以是常量、变量或表达式;操作符是表示比较、数学计算或逻辑运算的符号,也可以是SAS函数或者括号组。在SAS程序语句中,创建变量、赋值、求新值、转换变量和执行条件处理都会用到表达式。
表达式的例子如下:3、x、x+1、age<10、trim(last)||', '||first。SAS表达式的结果值是数字值、字符值或布尔值。
1.?操作数
操作数可以是常量、变量或表达式。SAS常量是表示一个固定值的数字或字符串。常量可用作许多SAS语句的表达式,包括变量赋值语句和IF-THEN语句,还可作为特定选项的值,例如OBS=5。SAS中存在4类常量:字符常量、数字常量、时间日期常量和位测试常量。
(1)字符常量
字符常量由1到32?767个字符组成,并且必须放在引号(单引号或双引号)中。在下面的SAS语句中,Tom是一个字符常量。
if name='Tom' then do;
如果字符常量包括单引号,则将该常量放入双引号中。例如,为了指定字符值Tom's,使用下面的形式:
if name="Tom's" then do;
或者将字符串放入单引号,并且用两个连续的单引号表示撇号。SAS将两个连续的引号作为一个引号。例如,要表示字符串Tom's,则使用下面的形式:
if name='Tom''s' then do;
要表示Tom"s,可以使用以下形式:
if name="Tom""s" then do;
使用引号一定要匹配,否则会致使SAS误读当前的错误语句以及跟随其后的语句。
字符常量还可以以十六进制形式表示。字符的十六进制常量是一个在单引号或双引号中偶数位的十六进制字符表示的字符串,并且该字符串后紧随字母X,例如'546F6D'x表示字符串“Tom”。引号中如果有前导或尾缀空格,则会出错,错误信息会写入日志中。可以使用逗号使引号中的字符串更可读。如果使用逗号,则逗号必须分隔该字符串的偶数个十六进制字符,例如'54,6F6D'x。
(2)数字常量
数字常量指的是SAS语句中出现的数字值。数字常量可以表示为标准计数法、科学计数法和十六进制计数法,例如1、-5、+49、1.23、01、2E23、0.5e-10等。对于大于1032-1的数字常量,必须使用科学计数法。
数字常量也可以以十六进制值表示。该十六进制值以数字(通常是0)开始,接着是多个十六进制字符,以字母X结束。常量最大可包含16个有效的十六进制字符(0~9、A~F)。例如,0c1x、9x。
(3)时间日期常量
在SAS中还可以创建日期常量、时间常量、时间日期常量。这些常量的形式为包含在单引号或双引号中的指定日期或时间,并接着跟随一个d(日期)、t(时间)或dt(日期时间)来说明值的类型。例如,'1jan2013'd、'9:25't、'01may12:9:30:00'dt。任何引号中包括的前导或尾缀空格都不会影响这些常量的处理。
(4)位测试常量
位测试常量是在引号中由0、1和点组成的字符串,而且其后紧跟b,例如'..1.0000'b。0用于测试该位是否为0,1用于测试该位是否为1,点(.)则表示忽略该位的测试。逗号和空格可插入位掩码中增加可读性。在位测试中,位常量用于对字符或数字变量进行位比较。当测试字符值时,SAS会将掩码的最左位与字符串的最左位对齐,测试则从左往右逐位处理对应位。当测试数字值时,数字值会从浮点数截断为32位整型,SAS将掩码的最右位与值的最右位对齐,然后往左逐位处理对应位。
下面的例子用于测试字符型变量a:
if a='..1.0000'b then do;
如果a(从左开始计数)第3位为1,并且第5~8位都是0,则该比较为真,并且表达式a='..1.0000'b的结果为1;否则,比较为假,表达式值为0。
位掩码不可以用作赋值语句中的位值。例如下面的语句是无效的:
x='0101'b; /* incorrect*/
$BINARYw.和BINARYw.格式,以及$BINARYw.、BINARYw.d和BITSw.d 输入格式在位测试中非常有用。可以将字符和数字值转换为对应的二进制值进行位测试。
(5)变量
变量是一组描述给定特性的数据值,可用于表达式中。
如果在一个表达式中指定了变量,但是变量值不匹配需要的类型,例如,在需要数值变量的地方使用了字符变量,或者相反,在需要字符变量的地方使用了数值变量,SAS则会尝试将该变量值转换成所期望的类型。SAS会按照如下规则自动在字符变量和数值变量之间转换:
如果使用要求数字操作数的操作符(例如加号+)时指定了字符变量,SAS将字符变量值转换为数字。
在使用比较操作符比较字符变量和数值变量时,SAS会将字符变量值转换为数字。
如果使用要求字符操作数的操作符(例如级联操作符)时指定使用了数值变量,SAS使用格式BEST12.将数值变量值转换为字符。关于格式BEST12.,请参考SAS帮助文档。
如果在赋值语句的左侧使用了数值变量而右侧是字符变量,SAS会将字符变量值转换为数字。反之,当左侧是字符变量而右侧是数值变量时,SAS会使用格式BESTn.将数值变量值转换为字符。其中,n是左侧变量的长度。
当执行自动转换时,SAS会在日志中打印提示信息,表明发生了转换。如果将字符变量值转换成数字时产生了无效的数字值,那么表达式的结果是缺失值,并且会在日志窗口打印错误消息,同时,会将自动变量_ERROR_设置为1。
还可以通过PUT和INPUT函数转换数据值。这些函数比自动转换会更有效,本章后面的章节会介绍。
2.?操作符
操作符包含算术操作符、比较操作符、逻辑操作符等,分别用于算术运算、比较表达式和对布尔值进行操作等。此外,它还提供了一些只能用于WHERE语句或WHERE=选项的操作符。
(1)算术操作符
使用算术操作符的表达式其运算结果是数值。表3.1给出了算术操作符的定义、示例及示例表达式的计算结果。
(2)比较操作符
使用比较操作符的表达式其运算结果是真(1)或假(0)。表3.2给出了SAS的比较操作符、等效字符、说明及使用该操作符的例子。
在对数字值进行比较时,SAS会基于值进行比较。缺失数字值小于任何其他数字值。表达式为真时,表达式的结果是1或真(true);表达式为假时,表达式的结果是0或假(false)。比较操作符常用于IF-THEN语句中,如以下例子:
if x<y then c=5;
else c=12;
也可在赋值语句表达式中使用比较,例如:
c=5*(x<y)+12*(x>=y);
这时,SAS先计算括号内表达式(x=y)的值(为0或1),然后使用计算结果替代括号里的表达式。因此,假设x=6,y=8,那么赋值语句c=5(1)+12(0)的结果是c=5。
记住,比较不同长度的数字值时可能会产生不正确的结果,因为小于8字节的值比8字节的值有更小的精度。另外,四舍五入也会影响数字比较的输出。
字符操作数的比较也会产生数字值1(或真)或0(或假)。SAS会从左至右逐个字符对字符操作数进行比较。空格和缺失值小于其他任何可打印字符值。字符顺序依赖于计算机的排列顺序,此顺序通常指的是在ASCII或EBCDIC编码中的顺序。例如在EBCDIC和ASCII的排列顺序中,G大于A。因此,表达式'Gray'>'Adams'的值为1或真。
如果是不同长度的两个字符值进行比较,在比较之前,SAS会假设已经用空格补充到了较短的字符操作数结尾处,使两个字符值有了相同的长度。在比较中尾缀空格会忽略,所以'fox '等于'fox'。然而,在字符值开始处和中间的空格都会参与比较,所以,' fox'不等于'fox'。
还可以在比较操作符之后使用“:”来比较字符表达式的指定前缀。SAS会在比较过程中截断较长的值使其与较短值的长度一致。在下面的例子中,在等于符号后面的冒号修改器告诉SAS仅查看变量LastName的第一个字符是否为S。
if lastname=:'S';
(3)逻辑(布尔)操作符
使用逻辑操作符的表达式其运算结果是布尔值,即为真(1)或假(0)。表3.3给出SAS的逻辑操作符、等效字符、示例及其说明。
(4)其他操作符
在SAS表达式中还可以使用一些其他操作符,例如级联操作、括号等,如表3.4所示。
在表达式,特别是包含多个操作符的复合表达式中,经常会将一些子表达式放入括号()中,表示优先对括号中的表达式求值,同时也会提高表达式的易读性。
级联操作符||会将操作符两侧的字符值进行级联。通常会使用赋值语句将级联操作的结果存储在结果变量中。如果事先没有通过LENGTH或ATTRIB语句指定该结果变量的长度,则其长度为在级联操作中每个变量或常量的长度总和。
级联操作不会去除操作数的前导和尾缀空格。如果变量带尾缀空格,在级联前使用TRIM函数可去除值中的尾缀空格。如果要去除操作数的前导和尾缀空格,通常使用表达式TRIM(LEFT(char))。
(5)WHERE语句操作符
在WHERE语句或数据集选项WHERE=中使用的表达式称为WHERE表达式。在WHERE表达式中除了可以使用上述操作符之外,还可以使用如表3.5所示的操作符。注意,这些操作符只能在WHERE表达式中使用。
关于这些操作符的详细使用情况,请参考SAS帮助文档。
(6)MIN、MAX操作符
MIN(><)和MAX(<>)操作符分别用于找到两个操作数中的最小值和最大值。例如,如果AB的返回值为B。如果比较中包含缺失值,SAS使用缺失值的排序顺序。
注意,在WHERE语句或WHERE从句中,<>操作符等同于NE。
3.?复合表达式求值
仅包含一个操作符的表达式为简单表达式。为了表示复杂的逻辑或操作,表达式中通常会包含多个操作符,这样的表达式称为复合表达式。复合表达式中经常使用括号对操作数进行分组。当遇到复合表达式时,SAS会遵循下述规则确定计算表达式各部分的顺序:
如果复合表达式中有括号,SAS会先对在括号中的表达式求值,再对括号外的表达式求值。
表3.6中给出了不同组的优先级。SAS先对组Ⅰ中的表达式部分求值,然后依次对组Ⅱ、组Ⅲ等组求值。
表3.6中也给出了同组内的求值顺序。对组Ⅰ中的操作符是从右到左,而其他组的操作符都是从左到右。
3.2.2 选取部分观测
在建立新数据集时,有以下两种方式可以从已经存在的数据集中选取观测到新数据集中。
通过删除不满足条件的观测来保留想要的观测。
仅接受满足条件的观测。
条件可以由IF语句、WHERE语句或数据集选项WHERE=中的条件表达式来指定。WHERE语句和数据集选项WHERE=可以用在DATA步和PROC步中,两者的使用方法基本相同,本书第5章会介绍它们在PROC步中的使用。本节主要介绍如何使用各种IF条件语句,以及同时使用IF-THEN/ELSE语句和OUTPUT语句,将已经存在的数据集中的观测根据给定条件一次或多次写入一个或多个输出数据集中。
1.?使用DELETE语句删除满足条件的观测
在DATA步中可以结合使用IF语句和DELETE语句来删除满足条件的观测,其基本形式如下:
IF 条件表达式 THEN DELETE;
在该过程中,SAS首先判断条件表达式是否为真。如果为真,则执行THEN从句中的DELTET语句。DELETE语句会让SAS立即返回DATA步的开始处读取下一条观测,当前观测不会写入输出数据集中。注意,DELETE语句不会删除输入数据集中的观测。
例3.4:在数据集saslib.contact中选取地址信息(Address)不为缺失值的观测建立新数据集work.contact_address。
已经存在的数据集saslib.contact中包含客户联系信息,内容如图3.4所示。
在DATA步中IF语句用于判断Address信息是否为缺失值,DELETE语句则可将Address为缺失值的观测删除。代码如下:
data work.contact_address;
set saslib.contact;
if address = "" then delete;
run;
proc print data=work.contact_address;
run;
PRINT过程打印的数据集work.contact_address如图3.5所示。其中不包含编号(Customer_ID)为C004的客户信息,因为其地址信息为缺失值。
2.?使用取子集的IF语句接受满足条件的观测
选择满足条件的观测另一种方式是直接选取满足条件的观测,即使用取子集的IF语句(Subsetting IF),其基本形式如下:
IF 条件表达式;
当条件表达式的值为真时,继续处理该观测;否则停止处理该观测并返回DATA步开始处读取下一条观测,且该观测不会写入输出数据集。该IF语句称为选取子集的IF语句,是因为所产生的输出数据集是原始数据集的子集。
例3.5:选取数据集saslib.contact中其职位(Position)为Manager的观测,建立新数据集work.contact_manager,并打印新生成的数据集中的数据。
代码如下:
data work.contact_manager;
set saslib.contact;
if position = "Manager";
run;
proc print data=work.contact_manager noobs;
run;
PRINT过程打印的数据集数据如图3.6所示。
当DELETE语句和取子集的IF语句都可以完成相同的操作时,选择哪种方式呢?主要依据如下:
由于DELETE语句和取子集的IF语句需要构造的条件表达式不同,因此在编写程序的时候,通常选择需要较少比较次数的条件表达式的语句,因为这会提高程序执行效率。
在比较次数相近时,通常选择正向的条件表达式,也就是说选择取子集的IF语句。
当数据中存在缺失值或可能有拼写错误的数据值时,使用取子集的IF语句也更容易产生需要的结果。
3.?使用OUTPUT语句建立多个数据集
结合条件语句和OUTPUT语句可以在一个DATA步中创建多个SAS数据集,以便分别包含输入数据集的不同观测。使用OUTPUT语句的基本形式如下:
OUTPUT <数据集>;
OUTPUT语句将当前观测写入指定数据集。其数据集必须为在DATA语句中出现的数据集。当未指定数据集时,SAS会将当前观测写入DATA语句的所有数据集中。
例3.6:选取在数据集saslib.contact中选取职位(Position)为Manager的观测,建立新数据集work.contact_manager,对其他职位的观测建立新的数据集work.contact_others。
代码如下:
data work.contact_manager
work.contact_others;
set saslib.contact;
if position = "Manager" then output work.contact_manager;
else output work.contact_others;
run;
proc print data=work.contact_manager noobs;
title "Postion is Manager";
run;
proc print data=work.contact_others noobs;
title "Postition is not Manager";
run;
PRINT过程语句打印的数据集数据如图3.7所示。
如果在DATA步中不使用任何OUTPUT语句,在每次迭代结束时会自动将观测写入DATA语句指定的所有数据集中。但是,如果在DATA步中使用了OUTPUT语句,每次迭代结束时就不会自动将观测写入任何数据集。因此,一旦在程序使用了OUTPUT语句,则必须为所有需要写入数据集的观测使用OUTPUT语句。此外,还需要注意的是,如果在OUTPUT语句之后进行计算,生成或改变的变量值也不会写入输出数据集。
SAS在执行完OUTPUT语句后,观测会一直存在于PDV中,直到本次迭代结束。所以在一次迭代中可以多次使用OUTPUT语句将PDV中的观测写入一个或多个数据集。
例3.7:在saslib.contact中选取职位(Position)为Manager的观测输出到数据集work.contact_manager中,并将年龄(Age)大于等于35的联系人的观测输出到数据集work.contact_agege35中。
代码如下:
data work.contact_manager
work.contact_agege35;
set saslib.contact;
if position = "Manager" then output work.contact_manager;
if age >= 35 then output work.contact_agege35;
run;
proc print data=work.contact_manager noobs;
title "Position is Manager";
run;
proc print data=work.contact_agege35 noobs;
title "Age is older than 35";
run;
PRINT语句打印的两个数据集内容如图3.8所示。其中Customer_ID为C001的观测同时存在于两个数据集work.contact_manager和work.contact_agege35中,因为其同时满足两个IF语句的条件。
3.2.3 操作所选取的观测
除了将满足条件的观测值直接写入结果数据集外,在DATA步中还可以操作满足条件的观测中的变量值,例如修改变量名字以及生成新变量等。
1.?IF-THEN/ELSE语句
SAS选取观测进行操作时,最常用的方式是通过IF-THEN/ELSE语句。其基本形式如下:
IF 条件表达式 THEN 可执行语句;
其中:
条件表达式是一个或多个SAS表达式,通常为由比较操作符和操作数组成的表达式。
SAS会对条件表达式的求值,结果为真(true)时,执行THEN从句中的可执行语句;条件表达式的值为假(false)时,SAS忽略THEN从句。可执行语句必须是在DATA步的单次迭代中可以执行的SAS语句。最常用的可执行语句是赋值语句。
ELSE从句提供对该观测的可选操作。当条件表达式为真(true)时忽略该从句;为假(false)时,执行ELSE从句中指定的语句。ELSE从句可以不存在,如果存在则必须紧跟在对应的IF-THEN语句之后。
例3.8:在saslib.inventory中包含了产品在各地区的库存和价格信息。公司现决定将每种产品在北京的销售价格提高20%,其他地区保持不变。
代码如下:
data work.Inventory2;
set saslib.Inventory;
if Region="BJ" then
Price=Price*1.2;
run;
proc print data=work.Inventory2 noobs;
run;
PRINT过程打印价格提高后的数据集内容如图3.9所示。
例3.9:公司决定将每种产品在北京的销售价格提高20%,其他地区销售价格提高10%。
这时可使用ELSE从句,代码如下:
data work.Inventory3;
set saslib.Inventory ;
if Region="BJ" then
Price=Price*1.2;
else Price=Price*1.1;
run;
proc print data=work.Inventory3 noobs;
run;
PRINT过程打印价格提高后的数据集内容如图3.10所示。
2.?赋值语句
赋值语句是可执行语句,常用于对变量进行赋值。其基本形式为:
变量=表达式;
其中:
变量是已经存在的变量或新变量,可以是单个变量名、数组引用或左侧的SUBSTR函数(即SUBSTR函数出现在赋值操作符左侧)等。若变量已经存在,赋值语句会修改该变量的值;如果变量不存在,赋值语句会创建新变量。
表达式是任何SAS表达式。
赋值语句用于对等号(=)右侧的表达式求值,并将结果存储在等号(=)左侧的变量中。表达式中可包含出现在等号左侧的变量。这时,变量的原始值用于对表达式求值,并且结果存储在等号左侧的变量中。
在例3.8中,使用了赋值语句(Price=Price1.2;)提高产品价格。在处理第一条观测时,输入数据集中的Price值为125,对表达式Price1.2求值,结果为150,因为左侧变量为Price,所以结果仍然存储在Price中。因此,输出数据集中Price的值为150。
3.?DO语句
DO语句也是可执行语句。通过DO语句可以将一组可执行语句指定为一个单元来执行。DO语句的基本形式如下:
DO;
…可执行语句组…;
END;
在DO语句和END语句之间的语句称为DO组(DO Group)。DO语句也可以嵌套DO语句。在IF-THEN/ELSE语句中,简单的DO语句通常用于根据IF条件是否为真来指定要执行的一组可执行语句。
例3.10:基于数据集saslib.Inventory中的数据,将每种产品在北京的销售价格提高20%,库存增加1倍,其他地区的销售价格提高10%,库存增加50%。
这时在IF语句THEN从句和ELSE从句中使用DO语句,代码如下:
data work.Inventory4;
set saslib.Inventory;
if Region="BJ" then
do;
Price=Price*1.2;
Instock=Instock*2;
end;
else
do;
Price=Price*1.1;
Instock=Instock*1.5;
end;
run;
proc print data=work.Inventory4 noobs;
run;
PRINT过程打印价格所生成的数据集内容如图3.11所示。
4.?嵌套IF-THEN/ELSE语句
一条IF-THEN/ELSE语句可根据条件提供两种不同的可选操作。很多时候需要根据一系列互斥的条件执行不同的操作,这时可使用嵌套的IF-THEN/ELSE语句。两层嵌套的IF-THEN/ELSE语句的基本形式如下:
IF 条件表达式1 THEN 可执行语句1;
ELSE IF条件表达式2 THEN可执行语句2;
ELSE可执行语句3;
其执行过程如下:
1)SAS计算条件表达式1的值。
2)当条件表达式1的结果为真时,SAS执行可执行语句1,并忽略紧跟其后的ELSE从句,包括嵌套的IF-THEN/ELSE语句。所有执行过程完成。
3)当条件表达式1的结果为假时,忽略紧跟其后的THEN从句,并进行下一步。
4)判断SAS条件表达式2的值。
5)当条件表达式2的结果为真时,SAS执行可执行语句2,并忽略紧跟其后的ELSE。所有执行过程完成。
6)当条件表达式1的结果为假时,忽略紧跟其后的THEN从句,SAS执行可执行语句3。所有执行过程完成。
例3.11:基于数据集saslib.Inventory中的数据,公司决定将每种产品在北京的销售价格提高20%,在上海的销售价格提高15%,其他地区的销售价格提高10%。
使用嵌套的IF-THEN/ELSE语句进行处理的代码如下:
data work.Inventory5;
set saslib.Inventory;
if Region="BJ" then
Price=Price*1.2;
else if Region="SH" then
Price=Price*1.15;
else
Price=Price*1.1;
run;
proc print data=work.Inventory5 noobs;
run;
PRINT过程打印各地区价格提高后的数据集内容如图3.12所示。
5.?SELECT语句
还可以通过SELECT语句构造不同的条件来操作观测。SELECT语句的基本形式如下:
SELECT <select-表达式>;
WHEN when-表达式 可执行语句;
<…WHEN when-表达式 可执行语句; >
<OTHERWISE 可执行语句;>
END;
其中:
select-表达式指定计算单个值的SAS表达式。
when-表达式为任意SAS表达式。在SELECT语句和END语句之间的语句称为SELECT组。SELECT组中要求至少有一条WHEN语句,而WHEN语句中要求至少有一个when-表达式。when-表达式为真时,执行跟随其后的可执行语句;否则,忽略其后可执行语句。
可执行语句可以是任何可执行的SAS语句,包括赋值语句、DO语句、SELECT语句或空语句等。空语句用于WHEN语句中时,SAS会认为该条件为真,但是不做任何操作。
OTHERWISE从句中指定当所有的WHEN条件都不满足时需要执行的语句。
例3.12:同例3.11一样,将公司每种产品在北京的销售价格提高20%,在上海的销售价格提高15%,其他地区的销售价格提高10%。
使用SELECT语句的代码如下:
data work.Inventory6;
set saslib.Inventory;
select (Region);
when ("BJ") Price=Price*1.2;
when ("SH") Price=Price*1.15;
otherwise Price=Price*1.1;
end;
run;
proc print data=work.Inventory6 noobs;
run;
PRINT过程打印数据集work.Inventory6的内容与work.Inventory5一样,这里不再给出。
3.2.4 分组与排序
SAS对数据集进行操作时,经常需要在SET、MERGE、MODIFY或UPDATE语句中使用分组数据。使用分组数据最基本的方法是使用BY语句,其基本形式如下:
BY 变量列表;
BY语句除了可用于DATA步中对数据集进行操作外,也可以用于SAS PROC步。在这些地方使用分组数据时,要求所有的观测必须按BY语句中的变量以数字或字符顺序升序或降序排列,或者以某种方式分组,例如以日历的月份或格式化后的值为条件进行分组。如果数据不满足这个条件,可使用SORT过程对其进行排序分组。
本小节会介绍如何对数据集中的变量排序,以使数据集符合分组需要,以及在处理单个数据集时分组数据的运用。本书第4章也会介绍分组数据的使用。
1.?使用SORT过程对观测进行排序
使用SORT过程的基本形式如下:
PROC SORT DATA=输入数据集 <OUT=输出数据集> <其他选项>;
BY 变量列表;
RUN;
其中:
输入数据集指定需要排序的数据集。
选项OUT=指定存储排序后数据的新数据集。当该选项不存在时,排序生成的数据写入由选项DATA=指定的数据集。输出数据集可以和输入数据集相同,不过此时会覆盖输入数据集。当输出数据集与输入数据集不同时,会创建新数据集。
还可以指定其他选项。常用选项如表3.7所示。
变量列表指定排序变量,可以是一个变量或多个变量。当指定多个变量时,SAS首先会按照第一个变量分组,然后在同一个分组内依照变量列表中的其他变量逐个进行排序。
例3.13:对公司员工先按照部门(Dept)名称进行排序,在同一部门里按照入职日期(Entry_Date)进行排序。
公司员工所在数据集saslib.employee的部分数据如图3.13所示。该数据集包含员工编号、姓名、所在部门、职位和入职年份。
使用SORT过程对该数据集进行排序时,在BY语句中先后指定排序的变量Dept和Entry_Date。
proc sort data=saslib.employee out=saslib.employee_sorted;
by Dept Entry_Date;
run;
排序后生成的数据集saslib.employee_sorted在VIEWTABLE窗口中打开如图3.14所示。
默认情况下,SAS根据BY变量的值升序排列分组。
2.?使用选项DESCENDING对观测按变量降序排序
在BY语句中,还可以在每个变量之前指定选项DESCENDING对变量进行降序排序,或者根据需要对部分变量进行升序排序、部分变量降序排序。其基本形式如下:
BY <DESCENDING > 变量1 << DESCENDING > 变量2...>;
如果变量前面存在选项DESCENDING,则该变量在组内按降序排序,否则按默认的升序排序。
例3.14:首先将saslib.employee中的员工数据按部门名称进行排序(升序),每个部门内部的入职日期由近到远进行排序(降序)。代码如下:
proc sort data=saslib.employee out=saslib.employee_descending;
by Dept descending Entry_Date;
run;
所生成的saslib.employee_descending在VIEWTABLE窗口打开如图3.15所示。
3.?找到分组中的第一个和最后一个观测
在使用BY语句时,SAS会自动为BY语句中指定的每个变量生成两个临时变量:FIRST.BY变量和LAST.BY变量。当变量值在每个分组中第一次出现时,FIRST.BY变量为1,否则为0;当变量值在每个分组中最后一次出现时,LAST.BY变量为1,否则为0。通过这两个变量可以找到分组中的第一个和最后一个观测,并进行相应的处理。在DATA步中使用SET语句和BY语句的基本形式如下:
DATA 数据集;
SET 数据集;
BY 变量列表;
...其他语句;
RUN;
例3.15:取排序生成的saslib.employee_sorted中每个部门最先入职和最后入职的员工,生成新数据集saslib.employee_fl。
代码如下:
data work.employee_fl;
set saslib.employee_sorted;
by Dept;
if first.Dept or last.dept;
run;
proc print data=work.employee_fl noobs;
run;
PRINT过程打印的数据集中的数据如图3.16所示。
4.?使用选项NODUPKEY删除重复BY变量的观测
使用SORT过程的NODUPKEY可以在对数据集按BY变量进行排序的同时,删除数据集中BY变量值相同的观测。
例3.16:从原始数据文件contact2.csv读取数据到SAS数据集saslib.contact2_raw,并对该数据集进行排序。排序输出数据集saslib.contact2中不包括BY变量值相同的观测,删除的观测保存在数据集work.contact2_dup中。
数据文件contact2.csv中包含的数据记录如下:
Name,Age,Position,Marriage,Address
Greg William,42,Manager,Single,"14 Bridge St. San Francisco, CA"
Emily Cooker,33,Sales,,42 Rue Marston
Henry Cooper,,Office,Married,52 Rue Marston Paris
Greg William,,Manager,Single
Jimmy Cruze,34,Manager,Single
Adam Smith,45,Sales,,"111 Clancey Court, NC"
读取数据文件contact2.csv中的记录创建数据集,并对其进行排序。要求排序输出的数据集中不包含Name值相同的观测,而且被删除的观测保存在另外的数据集中。
proc import out=saslib.contact2_raw
datafile="c:\sas\data\ch3\contact2.csv"
dbms=csv replace;
getnames=yes;
datarow=2;
run;
proc print data=saslib.contact2_raw noobs;
title "All Observations";
run;
proc sort data=saslib.contact2_raw
out=saslib.contact2
dupout=work.contact2_dup
nodupkey;
by Name;
run;
proc print data=saslib.contact2 noobs;
title "Observations with Duplicate BY Values Deleted";
run;
proc print data=work.contact2_dup noobs;
title "Duplicate Observations";
run;
PRINT过程打印的数据集内容如图3.17~图3.19所示。
在SORT过程中,输入数据集saslib.contact2_raw中的第4条观测(因为其名字为“Greg William”,与第1条观测重复)未写入数据集saslib.contact2,而是写入work.contact2_dup中。