3.2 数值型变量与字符型变量
变量类型或数据类型是一门语言的基础,它构成了一门语言变量的结构基础。
学过其他语言的朋友们,应该听说过很多变量类型的名字,例如Java语言中的整型、长整型、浮点型、双浮点型、布尔型、字符型等,在编程过程中如果没有选择合适的变量类型,会为后续编程带来很多烦恼,不得不返工。
在第1章中,我们提到SAS是一门高级语言。所谓高级,就是与我们的本能反应更加接近,不需要花额外的时间成本来记忆与理解,让编程者将注意力放在编程本身上。在数据类型上,SAS的数据类型只分为字符型和数值型,不包含更复杂的分类。下面分别介绍两种变量类型及它们对应的函数。
3.2.1 两种变量的概念
数值型变量用于存储数字,长度为3~8位,最大值为253,对于日常分析已经足够了。在创建数值型变量的时候,SAS会默认将变量的长度设置为8。其他长度可以使用length语句进行定义,例如以下语句:
执行后可以获得数据集new,变量var1的长度为3,值为256。
在日常编程时,我们一般不用考虑数值型变量的长度问题。如果涉及项目要求,可以参照表3-4所示的数值变量长度与可存储数的对应关系,选择合适的数值变量长度。
表3-4
长度的选择依赖于对数据的预估。例如数据集为世界国家与人口数量的数据,考虑到人口最多的国家的人口数不超过20亿,我们设定变量的长度至少为6。数值型变量的长度会影响变量被存储时所占的空间,但考虑到SAS优秀的内存管理能力,我们并不需要过分关切数值型变量的长度。
字符型变量用于存储字符串,长度可以自己定义,如果没有定义,则会按照创建时所需的最短长度自动定义。例如,运行以下代码:
执行结果如图3-13所示,生成的var2的长度就与其内容“Hello,World”一样为12。
图3-13
SAS的设定可以尽量地节约存储空间,却也会为我们编程带来一些不便。
例如运行以下程序:
运行结果如图3-14所示。
图3-14
生成一个药品名称的数据集,手动输入3种药物的名字,可以看到前两条记录的药品名称正常显示,而最后一条记录的药品名称却被截取了。这是因为当第一条记录Ibuprofen创建的时候,变量drug的长度按照Ibuprofen已经设置为9,后来的数据不会影响变量的长度。第二条记录Aspirin长度为7,所以可以全部显示,第三条记录Acetaminophen的长度为13,但因为变量长度已经设置为9,超过9的长度会被截取,截取过程如图3-15所示。
图3-15
在工作的时候,如果涉及定义字符型变量,我们要考虑它的长度,在创建之前进行定义。SAS的定义变量长度的方法有很多,最简单的方法为使用length语句,例如如下代码:
运行之后生成3条记录的数据集,包含一个变量DrugName,在为变量赋值前,我们已经使用length语句定义DrugName的长度为200,所以3条记录中的内容都被完整保留。注意,在设定长度的时候,字符型变量长度前要加$符号,而数值型变量不需要。
3.2.2 数值型变量的相关函数
函数是很多编程语言的基础,它是组织好的、可重复使用的、用来实现单一或相关联功能的封装功能块,函数一般由函数体和参数两部分构成,结构为:函数名(参数1,参数2,…)。函数名是SAS系统定义的实现特定功能的封装功能,不区分大小写;参数紧跟函数名,在括号之内,根据括号内参数的数量,函数可以分成单参函数和多参函数,多个参数之间使用逗号分隔。如果参数使用不当,SAS会停止运行并报错。
SAS提供了大量函数,用于计算或文本处理,熟练使用这些函数并加以组合可以更快地完成数据分析工作。最简单的计算功能可以用+、-、*、/、^完成,它们可以分别完成加减乘除和乘方的操作。SAS也提供了大量用于相对复杂计算的函数。
表3-5中所示的函数可以对数值型变量进行运算。
表3-5
其中,前5个函数后面的参数可以接无数个,以逗号隔开。
例如以下代码:
运行后获得如图3-16所示的结果。average为变量day1与day2的平均值。
图3-16
注意:data步中的函数只能进行“同行操作”,即一个函数只能处理同一行的多个变量,而无法处理一个变量的多条记录,例如在上述案例中,mean函数只能计算每一条记录中的day1与day2的平均值,而不能获取day1变量在所有记录中的平均值。
如果想要获取某变量在所有记录的平均值,SAS可以做到吗?答案当然是可以。只不过这将用到proc步骤,这是SAS独有的数据处理功能,我们将在下一章有所涉及。
有时在处理数字时,我们需要对数字取近似数,这就要用到表3-6所示的3种函数之一。
表3-6
如图3-17所示的数据,每个数字都有较多的小数点位数,比较和查看起来非常麻烦。
图3-17
我们可以运行以下代码:
运行后得到如图3-18所示的结果。
图3-18
生成的3个变量中,ceil变量让数字在整数位处直接进位,floor直接去掉了整数位之后的数值,而round按照四舍五入法保留了两位小数,即精确到0.01。round函数如果不指定精确位数,会自动保留至整数位,这是round函数中的默认设置。在SAS中,函数的缺省参数非常常见,在使用中我们可以逐步记忆,有些函数甚至通过改变缺省的参数可以实现完全不同的功能。
另外,SAS还提供三角运算函数sin()、cos()、tan()等,也提供对数运算log(),还有随机数生成,例如rand()。SAS中的运算函数其实非常多样,因为篇幅所限我们不得不删繁就简,毕竟语言学习不能靠机械地记忆,而要在实践中应用。
3.2.3 字符型变量的相关函数
让我们把思维从数字运算中跳出来,开始学习字符型变量的相关函数。相比起数字的精确,字符型变量能容纳的信息更多,也更接近真实生活中产生的数据,注重字符型变量中的信息挖掘,可以让我们在数据分析中获得更多有效的信息,而这就需要用到字符型变量相关的函数了。
字符型变量的处理可以简单地分为3类:筛选、缩减和转换,按照这3个类别看看每种类别都包括什么函数。
1.字符串的筛选
字符串的筛选是指给定条件,选出字符串的内容或某些内容出现的位置,主要包含表3-7所示的函数。
表3-7
下面举例说明,例如我们有如下变量:
(1)使用函数substr(),可以截取我们想要的字符内容,例如substr(string,8,5)可以截取字符串World,它从变量string中,从第8位开始连续截取后续5位字符串,返回的值为World。这个函数的用法比较简单,这里提示读者,第三个参数的意义为截取的长度,而非截取的结束位,在刚开始使用时不要记错。另外,与其他某些编程语言不同,SAS记数是从1开始而非0,也就是说,以上字符串的第一个H的位置为1。
(2)使用函数index(),可以返回字符内容是否存在的信息,例如index(string,'W'),string字符串中含有字符W,因此返回的结果为1;若是使用index(string,'z'),则返回的结果为0。index()函数经常与if从句连用,用于筛选字符串中包含某些字符的记录。另外,当字符内容是多个字符的时候,index函数会依次比较每一个字符,只要有包含在字符串中的就会输出为1,在此例中index(string,'Wz')返回的结果依旧为1。如果想按照整个单词查找,需要使用index的相关函数indexw。
(3)scan()函数可以返回字符串按照分隔符分隔后的结果,例如希望将Hello,World与Hello,China分开,那么按照此方法使用scan(string,1,'!'),字符串会按照!分隔,分成如图3-19所示的3部分。
图3-19
第二个参数1则表示选取第一部分,即Hello,World。这里选取分隔后的区块,计数也是从1开始,若想选取Hello,China部分,则将第二个参数改为2。
(4)find()函数与index()函数的功能类似,只是返回的结果是字符内容的所在位置而非仅仅是1或0。例如find(string,'W')将返回数字8,因为字母W出现在该字符串的第八位,注意这里的位数也是从1开始计算。若某字符出现过多次,那么该函数会返回字符第一次出现的位置,例如find(string,'e'),字母e第一次出现的位置是2,返回值则为2。若字符不包含在字符串中,find()函数将返回0。
以上4个函数可以两两分组,按照返回值类型分类,find与index返回的结果都是数字,scan和substr返回的都是字符串,在使用时要注意区分,先确定已知情况和期望的结果,再选择合适的函数。
2.字符串的缩减
字符串的缩减是指从字符串中去掉某些内容,这里着重介绍compress()函数,compress是压缩的意思,是SAS中常用的字符串删减函数。它的基本语法为:
让我们由浅入深,认识一下这个函数。
有时,数据在输入的时候,输入人员会不小心添加多余的空格,为了方便后续处理,需要将字符串中的空格全部去掉,此时使用compress()函数,例如:
输出结果为“今天是个好日子”,文本前后和中间的空格全部被去掉,此时compress()函数只有一个参数,即需要处理的文本。这里注意,compress()函数在处理英文时,也会将单词之间的空格去掉,有时反而增加文本处理的难度。
某些时候,数据更加杂乱,例如某些论坛会限制一些词汇的使用,以塑造良好的网络环境,但聪明的网友想到了很多办法绕过限制词,例如在违禁词之间插入一些符号,使系统无法识别,此时使用compress()函数删除某些符号,再与限制列表对比,就能获得想要的结果,例如:
假设“抽烟喝酒”是违禁词,在它们中间插入符号@就无法被系统发现,此时的compress()功能是删除字符串“抽@烟@喝@酒”中的@符号,还原成本来面目,被我们一眼发现。当然,有人会使用多种分隔符,此时compress()函数也有办法,例如:
将想删除的文本加入第二个参数,compress()函数都会毫不留情地将它们删除。
现实数据的复杂性超过我们的想象,让我们再考虑考虑更复杂的数据。例如我们手中有一张成绩单如图3-20所示,包含学生的数学成绩,但成绩的录入员将学生的评级也录入到了数学一列。
图3-20
可以看到学生的成绩是由分数组成加上ABCDEF六种等级,我们希望获得的只有分数,聪明的读者一定想到了可以使用compress(score,'ABCDEF')来去除等级字母,这是一种很好的办法。但我们在工作中编程,要考虑程序的三用性:可用性、复用性和泛用性。可用性指程序解决目前需求的能力,复用性指程序解决相同结构需求的能力,泛用性指程序解决类似需求的能力,它们的关系如图3-21所示。
图3-21
三种性质的外延依次放大,我们编程时要在条件允许的情况下最大程度满足三性,才能使程序更加稳健。以上方法在可用性和复用性上没有问题,但泛用性较差,若评分等级更新,加入了其他等级,就需要修改程序。这里可以使用如下方法:
这里用到了compress()函数的第三个参数——操作符。操作符是某些函数独有的,具有特定指代意思的符号。例如这里的a就是指代全部字符型变量。Compress()函数有很多操作符,常用的如表3-8所示。
表3-8
操作符为了方便记忆,其实都是它表示内容的字母缩写,例如a就是alphabet的缩写,d是digit的缩写,i是ignore的缩写,k是keep的缩写,但我们在学习时不用特别记忆,而是在实践中用经验记忆,这才是最快最好的学习方法。
操作符可以多个一起使用,也可以与需要删除的字符一起使用,例如compress(variable,,'ad')表示删除变量variable中的字符和数字,compress(variable,'abc','ik')表示保留变量variable中的abc和ABC字符。
compress()函数的使用十分广泛,学好它可以让我们处理字符型变量更加灵活。需要注意的是,操作符的默认位置是compress的第三个参数,因此在没有第二个参数的情况下,需要连续输入两个逗号,表示第二个参数为默认。
除了compress()函数,表3-9中所示的函数也可以完成字符串的缩减功能。
表3-9
注意:以上函数的用法比较相似,在工作中我们要选择合适的函数加以使用。
3.字符串的转换
第三大类就是转换类函数。这一类的范围比较广,基本囊括了不是筛选和缩减的所有函数。例如函数translate()可以将字符型变量中的内容依次替换:
得到的结果为H1223,W3r2d!。字符串中的字母e被替换成了1,l成了2,o成了3,注意该函数的第二个参数为要变成的内容,第三个参数为原字符变量中有的字母,二者不要混淆。
有时,我们希望替换的不是某个字母,而是某个单词,这时就可以用到translate的相关函数tranwrd()了,具体语法为:
这样得到的结果就变成了Hello,China!,在tranwrd中,第二个参数为原字符变量中的字符串,第三个参数为希望变成的字符串,这与translate是相反的,需要特别留意。另外,我们之前提到过SAS中的值是区分大小写的,大写与小写的值在SAS中会被理解为不同的内容。因此如果使用tranwrd('Hello,World!','world','China');将第二个参数中的World改为world,执行结果仍然是Hello,World,因为SAS没有找到值world。
还可以计算一个字符型变量的长度,使用length()/lengthn()/lengthm()lengthc()函数。length()函数返回字符型变量去掉右空格的长度,例如length('abcde')的返回值为5,如果使用length('abcde '),它的返回值依旧为5,因为length()函数计算长度是去掉右侧空格的长度。
lengthn()函数在面对非空字符串时与length()函数完全相同,只是面对空字符串,length()函数会返回1,而lengthn()函数会返回0。
lengthm()函数返回的是字符串占用内存的长度,而lengthc()函数返回的是字符串包括结尾空格的长度。
我们用例子来说明:
先定义字符型变量a的长度为50,然后给a定义一个前后都有空格的非空值。
运行以上代码,结果如图3-22所示。
图3-22
length()函数与lengthn函数的返回值都是4,这是因为a的最后一位为空格,两个函数均不考虑;lengthm()函数与lengthc()函数的返回值为50,这是因为在length语句中我们定义了a的长度为50,所以它占用的内存和包含空格的总长度为50。
在使用length相关函数的时候,一定要注意区分它们的异同。
针对字符型变量,SAS中还有大量函数可供我们使用,因为篇幅所限不能一一介绍,表3-10所示为SAS字符型变量常用的函数,在实践中可以先按图索骥,再灵活运用,最后融会贯通。
表3-10
在本节最后,相信大家心中还有一个问题,那就是数值型和字符型变量是否可以互相转化呢?答案自然是可以,转化的方法也是使用相关函数。不过,在学习相关函数之前,需要了解SAS中的一个概念——数据格式,它是SAS灵活处理和显示数据重要的概念。下一节我们将探讨数据格式。