
3.1 逻辑判断与数据集合并
逻辑和判断一般是一门语言最基础的操作,也是最能体现编程语言结构的语句,而数据集的合并则是针对数据集最基本的操作。本节从最基础的SAS语法讲起,开始真正的数据处理。
3.1.1 SAS的基本语法特点
提起编程语言,很多有过编程经验的同学都会想起C、Java,从事数据分析的朋友们会想起Python语言和R语言,从事大数据行业的朋友可能最先想到的是Pig和Hive。目前各个行业的主流编程语言加起来有数十种之多,每种的语法都自成体系。幸好编程语言的设计是为了方便人类跟计算机交互,新语言也会借鉴旧语言的思路,很多编程的语法虽然有所区别,但大体上还是一致的。我们在学习一门新语言的时候,可以有意识地拿它和已知语言做对比,找出相同和不同之处,着重记忆区别,这样可以更快地入门。
首先了解一下SAS这门语言的独特设定,SAS语言的基本语法规则如下:
(1)语句不区分大小写,例如逻辑判断语句if a=b和IF A=B对SAS而言是相同的。
(2)值区分大小写,例如我们给一个变量赋值,a="Hello"与a="hello"是不等价的。值一般放在引号之中。
(3)SAS语言中,单引号(')和双引号(")等价,在编程时可以互换。需要注意的是,本条仅适用于非宏编程相关的语句,如果使用宏变量进行赋值或操作,只能使用双引号"才能解析出宏变量所代表的值,使用单引号不会解析,只会直接使用引号内的值。宏编程是比较高级的内容,第5章会有所涉及,现在大家记住单引号和双引号等价即可。另外,需要提示读者SAS只能识别英文输入法下的引号、逗号等符号,在编程时需要将输入法切换为英文状态避免犯错。
(4)SAS的每句独立语句需要以分号(;)结尾,换行、空格等无法被视为语句结束符号。
(5)SAS的变量名必须以字母或下画线为开头,例如_abc、c3、dna等,不能以数字作为开头,且总长度不能超过32个字符。大小写变量名等价。
3.1.2 data步与proc
SAS语言的主要操作都放在data步或proc之中,两者的区别我们在2.3节中简单说过,对于初学者,可以简单理解为data步用于数据处理,proc用于数据分析。但在实践中可以发现,二者的区别并不是泾渭分明,例如proc sort用于给数据集排序,这也可以算作数据集处理步骤。另外,有些操作既可以在data步中完成,也可以在proc中实现。表3-1为data步与proc的对比。
表3-1

3.1.3 逻辑判断语句
在上一章中,我们学习了简单的data步编程语句,其中提到使用keep可以保留选定的变量,形象地说就是将数据集“瘦身”,因为变量的缩减降低了数据集的宽度。那么有没有一种办法可以让数据集“变矮”呢?答案是有的,这就是if和where语句。
if和where语句是SAS中最常用的逻辑判断语句,主要用于数据筛选和条件赋值。
if语句的语法为if变量=值then…,该操作是如果变量等于某值,那么就进行then后的操作。
图3-1所示的数据中包含了世界上的部分国家名和人均GDP。国际公认的发达国家的门槛是人均2万美元及以上。可以编写并运行如下代码:

图3-1

这样就在数据集new中新增了一个变量country_type,如果数据中GDP大于20000,那么就给它赋值为“发达国家”,不满足条件的就不赋值,生成的数据集如图3-2所示。

图3-2
同时,if语句也可以进行数据筛选,在上面的例子中,如果将上述的if语句换成如下:

运行后可以获得如图3-3所示的结果。

图3-3
我们将会获得一个数据集,该数据集只包含GDP大于等于20000的数据,其他数据会被删除。有时,我们希望进行多分支的条件判断,此时可以使用if…else语句。
还是刚才的案例,将if语句替换为如下语句:

运行后可以获得如图3-4所示的结果。

图3-4
每一条记录都被保留,并且新增了一个变量country_type,根据不同的人均GDP得到了不同的country_type的值。
理论上,else的语句可以无限长,囊括所有条件判断和操作,但如果条件判断过多,为了简化程序,我们会使用select语句,其语法如下:

该语句的用途是根据某变量不同的值(值1、值2……)进行不同的操作,最后以OTHERWISE设定WHEN语句没有包含的情况下的操作,前例中的语句可以等价于:

除了if语句和select语句,where语句也可以达成类似的功能。需要注意的是,where语句只能用于数据筛选,而不能用于条件赋值。
where语句不仅可以在data步内部使用,还可以与data步平行使用,例如以下代码:

运行后得到的结果与图3-4相同。
那么where和if的区别究竟在哪里呢?这里我们就需要了解一下SAS的运行机制了。
SAS在读取数据集的时候,会先通过一个叫I/O Measurement的工具将数据读取到内存缓冲区,然后通过程序数据向量(PDV)创建出可供操作的数据集。在完成数据集处理后,I/O Measurement工具又将内存中的数据集发送给输出数据,SAS数据读取与输出过程如图3-5所示。

图3-5
从图3-5所示的过程中可以看出,数据集输入后并不是直接处理的,而是先存在内存的缓存区中,然后经过PDV过程才能进入真正的内存处理区。这就像我们去买古董,按照古董行的规矩,买家和卖家之间不能直接接手东西,否则古董在接手的时候不小心砸碎了,责任无法说清。这时候买家选定一件古董,卖家把它取过来后,先放在一张桌子上,然后买家从桌子上再把古董拿来鉴赏,这样就避免了递交过程中没有接好的责任不清问题。
了解了SAS数据读取的过程,if和where的区别就更好理解了。if语句是在PDV执行之后才执行,针对待处理数据进行筛选或赋值,而where语句是在PDV执行之前就已经被执行,当数据被读取至缓冲区的时候就已经被筛选完成。再用古董行业的例子来说明,if就相当于要求卖家把所有古董都拿到桌子上,买家自己一样一样挑选,而where则相当于买家坐在店中,对古董先有了大概的了解,只要求卖家把自己想买的古董拿来。
在使用时,where的执行速度会比if更快,而if的灵活程度更高。还是拿古董举例,if让卖家把所有东西都放到你面前,你尽可以随意选择,给每个古董贴上标签或者分类;where则是只让老板把你看上的古董拿到面前,老板完成你指令的速度可以更快。这里的古董店老板其实就是我们电脑的内存。
if与where在应用时的更多差别如表3-2所示。
表3-2

在数据量较小的时候,if和where的差别不大,刚开始学习SAS的同学们可以不用在意二者的区别。但如果数据量超过10000条,你就可以感受到二者之间的差别。如果超过了10万条,在明确了筛选条件的情况下建议使用where语句。
3.1.4 数据集的合并
在工作与学习中,我们经常要处理多个数据集,有时需要将它们合并。在SAS中,用于合并的语句是set和merge,它们可以将多个数据集合并成一个,不过合并方式略有区别。
set语句在之前的章节已经涉及,它是我们最简单的将数据集读取到work库中的方法,其用法如下:

这样就把存储在sashelp库中的数据集demographics放到了名为demo的数据集中,这是set语句最简单的用法,其实merge语句也可以完成相同的操作,在工作习惯上我们一般选择set。
当我们有两个或以上数据集,它们记录的是一个数据的不同部分时,可以使用set将它们合并到一起。例如有以下3个数据集(见图3-6),分别记录了学生的学号、姓名和成绩,数据集1是学号101和102的学生,数据集2是学号103~105的学生,数据集3是学号106和107的学生。

图3-6
为了在一个数据集中查看所有学生的成绩,我们要将数据集合并到一起。使用如下语句:

执行后得到的结果如图3-7所示。可以看到,数据集被纵向合并,生成了一个记录数更多的数据集。

图3-7
相对应,还可以横向连接数据集,这时要使用的语句是merge。例如,有如图3-8所示的两个数据集。

图3-8
a是学号为101和102学生的姓名和语文成绩,b是两人的数学成绩,此时我们希望的是得到一张既包含语文成绩也包含数学成绩的表,那么merge语句可以发挥作用,代码如下:

执行后得到的结果如图3-9所示。可以看到数据集被横向合并,生成了一个变量数更多的数据集。

图3-9
在很多时候,需要横向连接的数据集并不是按照记录一一对应,如图3-10所示的两个数据集。

图3-10
a是学号为101和102学生的语文成绩,b是学号为102和103学生的数学成绩,我们希望做到的合并是将每个人的学号和他的成绩对应起来,如果成绩不存在就留空。这时需要设置一个指示变量,一般称为键,作为两个数据集连接的传导,在此例中就是两名学生的学号,用紧随merge语句的by语句指定学生的学号和姓名为键。
执行以下程序:

所得到的数据集如图3-11所示。

图3-11
图3-11中两个数据集中的每条记录都按照学号横向合并到了一起,原数据中没有103号的数学成绩和101号的语文成绩,合并之后为空。在SAS中,数值型变量的缺失值以点(.)表示,字符型变量的缺失值以空白表示。同时,根据我们的“六合刀法”,by语句是附属在merge语句之下的,可以另外缩紧一列,让程序更有格式。
总结一下,set和merge语句有如表3-3所示的区别。
表3-3

最后请注意两点,SAS也支持SQL,如果你有SQL相关的知识,可以在SAS中使用SQL语句进行数据集的合并操作,此内容不是本章讨论的范畴,具体内容可以参见4.6节的内容。
读者在自己练习merge语句的时候,可能会出现报错的情况,日志内容如图3-12所示。

图3-12
产生该问题的原因是由于数据集没有按照by变量排序,对数据集排序要用到proc sort,之前的章节也有所涉及。关于proc sort更具体的用法我们会在下一章进行讲解。