![SAS数据统计分析与编程实践](https://wfqqreader-1252317822.image.myqcloud.com/cover/469/40778469/b_40778469.jpg)
3.2 数值型变量与字符型变量
变量类型或数据类型是一门语言的基础,它构成了一门语言变量的结构基础。
学过其他语言的朋友们,应该听说过很多变量类型的名字,例如Java语言中的整型、长整型、浮点型、双浮点型、布尔型、字符型等,在编程过程中如果没有选择合适的变量类型,会为后续编程带来很多烦恼,不得不返工。
在第1章中,我们提到SAS是一门高级语言。所谓高级,就是与我们的本能反应更加接近,不需要花额外的时间成本来记忆与理解,让编程者将注意力放在编程本身上。在数据类型上,SAS的数据类型只分为字符型和数值型,不包含更复杂的分类。下面分别介绍两种变量类型及它们对应的函数。
3.2.1 两种变量的概念
数值型变量用于存储数字,长度为3~8位,最大值为253,对于日常分析已经足够了。在创建数值型变量的时候,SAS会默认将变量的长度设置为8。其他长度可以使用length语句进行定义,例如以下语句:
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00076001.jpg?sign=1738804773-wRZd550LfPFFV1dIh90nriLCBbV5GmUz-0-f2331288c69f8bc8a1871c0ba4b8121d)
执行后可以获得数据集new,变量var1的长度为3,值为256。
在日常编程时,我们一般不用考虑数值型变量的长度问题。如果涉及项目要求,可以参照表3-4所示的数值变量长度与可存储数的对应关系,选择合适的数值变量长度。
表3-4
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00076002.jpg?sign=1738804773-yi5IBaLpmd83N2cHnFd92We6g5Ry30l4-0-fe85ea7ad1c61c2ffeb68f55dbefdc1d)
长度的选择依赖于对数据的预估。例如数据集为世界国家与人口数量的数据,考虑到人口最多的国家的人口数不超过20亿,我们设定变量的长度至少为6。数值型变量的长度会影响变量被存储时所占的空间,但考虑到SAS优秀的内存管理能力,我们并不需要过分关切数值型变量的长度。
字符型变量用于存储字符串,长度可以自己定义,如果没有定义,则会按照创建时所需的最短长度自动定义。例如,运行以下代码:
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00076003.jpg?sign=1738804773-BVQebxGvduddmoB6MOzIeKJsQoxSShzf-0-f2805a090df4e44f5d847b95dd3701af)
执行结果如图3-13所示,生成的var2的长度就与其内容“Hello,World”一样为12。
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00077001.jpg?sign=1738804773-GDVClKEOeJlcSTUnpWG4uuEmc29st1Wa-0-e4c9c0d95a0193de5410299535bcc0ce)
图3-13
SAS的设定可以尽量地节约存储空间,却也会为我们编程带来一些不便。
例如运行以下程序:
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00077002.jpg?sign=1738804773-9mP0Ah9skIquKLGb3wObxJIWZzhzOkRc-0-d779b75fbdd547c7dd6466c3a04434f2)
运行结果如图3-14所示。
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00077003.jpg?sign=1738804773-0magECbvZjSEfmpNqWi0kPiRM4ZAWgxD-0-b3720619993c0fd2c177cb985e1887b5)
图3-14
生成一个药品名称的数据集,手动输入3种药物的名字,可以看到前两条记录的药品名称正常显示,而最后一条记录的药品名称却被截取了。这是因为当第一条记录Ibuprofen创建的时候,变量drug的长度按照Ibuprofen已经设置为9,后来的数据不会影响变量的长度。第二条记录Aspirin长度为7,所以可以全部显示,第三条记录Acetaminophen的长度为13,但因为变量长度已经设置为9,超过9的长度会被截取,截取过程如图3-15所示。
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00077004.jpg?sign=1738804773-5CtvKm6BwVQGs5UpDmpS3Bn8eGEsIbRt-0-1e457233bbbef91ef686c8593523acff)
图3-15
在工作的时候,如果涉及定义字符型变量,我们要考虑它的长度,在创建之前进行定义。SAS的定义变量长度的方法有很多,最简单的方法为使用length语句,例如如下代码:
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00077005.jpg?sign=1738804773-zUOo8FgNrx3FIz3hJm94hthsV5cFtPMg-0-7bf5245c0f8569a08ead5428e217ae42)
运行之后生成3条记录的数据集,包含一个变量DrugName,在为变量赋值前,我们已经使用length语句定义DrugName的长度为200,所以3条记录中的内容都被完整保留。注意,在设定长度的时候,字符型变量长度前要加$符号,而数值型变量不需要。
3.2.2 数值型变量的相关函数
函数是很多编程语言的基础,它是组织好的、可重复使用的、用来实现单一或相关联功能的封装功能块,函数一般由函数体和参数两部分构成,结构为:函数名(参数1,参数2,…)。函数名是SAS系统定义的实现特定功能的封装功能,不区分大小写;参数紧跟函数名,在括号之内,根据括号内参数的数量,函数可以分成单参函数和多参函数,多个参数之间使用逗号分隔。如果参数使用不当,SAS会停止运行并报错。
SAS提供了大量函数,用于计算或文本处理,熟练使用这些函数并加以组合可以更快地完成数据分析工作。最简单的计算功能可以用+、-、*、/、^完成,它们可以分别完成加减乘除和乘方的操作。SAS也提供了大量用于相对复杂计算的函数。
表3-5中所示的函数可以对数值型变量进行运算。
表3-5
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00078002.jpg?sign=1738804773-hisybjlUpUn9tP5QtCdhC0Q6cFzj765T-0-1ae5e307533e75af4106b4fbfbc50593)
其中,前5个函数后面的参数可以接无数个,以逗号隔开。
例如以下代码:
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00078003.jpg?sign=1738804773-Tpmbqps7jy2Zxm6OnUrpiH1D5MDisAzt-0-f0e41c7cf48539f27e1f3900355eb3ba)
运行后获得如图3-16所示的结果。average为变量day1与day2的平均值。
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00079001.jpg?sign=1738804773-pPubZQ2FnDcxrXQrVX7Dl5UsamDRZby1-0-a5c486fc2513705782837db49f9eb6d0)
图3-16
注意:data步中的函数只能进行“同行操作”,即一个函数只能处理同一行的多个变量,而无法处理一个变量的多条记录,例如在上述案例中,mean函数只能计算每一条记录中的day1与day2的平均值,而不能获取day1变量在所有记录中的平均值。
如果想要获取某变量在所有记录的平均值,SAS可以做到吗?答案当然是可以。只不过这将用到proc步骤,这是SAS独有的数据处理功能,我们将在下一章有所涉及。
有时在处理数字时,我们需要对数字取近似数,这就要用到表3-6所示的3种函数之一。
表3-6
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00079002.jpg?sign=1738804773-DT5Tca7sZG1xtwsoHd2ovKJE9ZmuJ5zG-0-05fb5b4d618c65c5e3b39dccc0672750)
如图3-17所示的数据,每个数字都有较多的小数点位数,比较和查看起来非常麻烦。
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00079003.jpg?sign=1738804773-erkU7KaKwZRvYH5MJkhDZoRmV3gSdjYo-0-f566f80f1a0a9895dbdd40ea78f49f54)
图3-17
我们可以运行以下代码:
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00079004.jpg?sign=1738804773-xeIKmZTlfewtYHCD7Mw2JK3fba3TbUXm-0-5f42ebbff9141bbdbedcf9fa9d93bbcf)
运行后得到如图3-18所示的结果。
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00079005.jpg?sign=1738804773-O0CsZOkutDRt7lG0Z0hHaFuwfg8QRfdq-0-5521233e17c235146d204e70303e10ad)
图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
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00080001.jpg?sign=1738804773-2T4tQZxDtv3wX3faSAIIzV1dXGlMoYzr-0-2222d7f315887608829d183fede4989f)
下面举例说明,例如我们有如下变量:
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00080002.jpg?sign=1738804773-msFHfvZQLKDDNpBmdhTzg079KpUAiCyQ-0-c01f49252512eeb72bf013f28c7e9cff)
(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部分。
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00081001.jpg?sign=1738804773-HFFVkT8rYUjDbPrY1Hm8MfUWxzPEGwOn-0-bcc3ae9fd467aaa25d40b5b775dfffc0)
图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中常用的字符串删减函数。它的基本语法为:
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00081002.jpg?sign=1738804773-EG1SwpznUzypQUeAAFfJvXBhMJUTqDFb-0-fe655c037271c110887b3947cf51f1fb)
让我们由浅入深,认识一下这个函数。
有时,数据在输入的时候,输入人员会不小心添加多余的空格,为了方便后续处理,需要将字符串中的空格全部去掉,此时使用compress()函数,例如:
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00081003.jpg?sign=1738804773-bj3NOXZDWOl0Ofkc8GHQXM8UQ8O2BIxS-0-b132a3b45cd190d4bd911aa9667f192d)
输出结果为“今天是个好日子”,文本前后和中间的空格全部被去掉,此时compress()函数只有一个参数,即需要处理的文本。这里注意,compress()函数在处理英文时,也会将单词之间的空格去掉,有时反而增加文本处理的难度。
某些时候,数据更加杂乱,例如某些论坛会限制一些词汇的使用,以塑造良好的网络环境,但聪明的网友想到了很多办法绕过限制词,例如在违禁词之间插入一些符号,使系统无法识别,此时使用compress()函数删除某些符号,再与限制列表对比,就能获得想要的结果,例如:
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00082001.jpg?sign=1738804773-45gTYzSvFLqJusRjcoCP2pVIzCcM6ZbX-0-e09f6d4a8298c662949fe8b33413347a)
假设“抽烟喝酒”是违禁词,在它们中间插入符号@就无法被系统发现,此时的compress()功能是删除字符串“抽@烟@喝@酒”中的@符号,还原成本来面目,被我们一眼发现。当然,有人会使用多种分隔符,此时compress()函数也有办法,例如:
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00082002.jpg?sign=1738804773-44NQBnAnChkbLdrSuRG4P3mxUh40NCab-0-da85aa503de351cbe935728e4cbcd800)
将想删除的文本加入第二个参数,compress()函数都会毫不留情地将它们删除。
现实数据的复杂性超过我们的想象,让我们再考虑考虑更复杂的数据。例如我们手中有一张成绩单如图3-20所示,包含学生的数学成绩,但成绩的录入员将学生的评级也录入到了数学一列。
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00082003.jpg?sign=1738804773-GhUsW8ydmVYZJh4tZ0dB0oOmI3onEpIb-0-3f2842f8e98e4cf2cc7c6a29596a3b37)
图3-20
可以看到学生的成绩是由分数组成加上ABCDEF六种等级,我们希望获得的只有分数,聪明的读者一定想到了可以使用compress(score,'ABCDEF')来去除等级字母,这是一种很好的办法。但我们在工作中编程,要考虑程序的三用性:可用性、复用性和泛用性。可用性指程序解决目前需求的能力,复用性指程序解决相同结构需求的能力,泛用性指程序解决类似需求的能力,它们的关系如图3-21所示。
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00082004.jpg?sign=1738804773-wzdIN4iXi2DVV2lsjA98qDYkAo57Icqz-0-2e6cdd4c608c30a92e35023fde3e293d)
图3-21
三种性质的外延依次放大,我们编程时要在条件允许的情况下最大程度满足三性,才能使程序更加稳健。以上方法在可用性和复用性上没有问题,但泛用性较差,若评分等级更新,加入了其他等级,就需要修改程序。这里可以使用如下方法:
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00083001.jpg?sign=1738804773-nYoH2qI0XIV0YrIKLw6NVzfpaLJCAnDi-0-be8707abf95c51928de75b40302bec2e)
这里用到了compress()函数的第三个参数——操作符。操作符是某些函数独有的,具有特定指代意思的符号。例如这里的a就是指代全部字符型变量。Compress()函数有很多操作符,常用的如表3-8所示。
表3-8
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00083002.jpg?sign=1738804773-LOK0IzUhkIu40nIl0tgRkHx9C6H70EWr-0-e626625bcbf1d63c18ec5a1296245e28)
操作符为了方便记忆,其实都是它表示内容的字母缩写,例如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
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00083003.jpg?sign=1738804773-UgT4dqd6MoSbpI3JThVSuQfMfswvZI4s-0-1fd8103ff76b2024eb330ca1c8804c94)
注意:以上函数的用法比较相似,在工作中我们要选择合适的函数加以使用。
3.字符串的转换
第三大类就是转换类函数。这一类的范围比较广,基本囊括了不是筛选和缩减的所有函数。例如函数translate()可以将字符型变量中的内容依次替换:
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00084001.jpg?sign=1738804773-D26KZBf4i4ak9UMwrp72hDuAIvi0wqwu-0-5b0166b0701cf3cf52fe2980ee3cc41f)
得到的结果为H1223,W3r2d!。字符串中的字母e被替换成了1,l成了2,o成了3,注意该函数的第二个参数为要变成的内容,第三个参数为原字符变量中有的字母,二者不要混淆。
有时,我们希望替换的不是某个字母,而是某个单词,这时就可以用到translate的相关函数tranwrd()了,具体语法为:
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00084002.jpg?sign=1738804773-0DkPe8X3yA1SnL2LqXtuBAZNweZZd8Oz-0-bf1c02a65cc856e18e1588834335b6bf)
这样得到的结果就变成了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()函数返回的是字符串包括结尾空格的长度。
我们用例子来说明:
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00084003.jpg?sign=1738804773-nqwsoCdwY7k07nRfW1BrIiQO2UAPOh02-0-68c679195afa74800333681991f35751)
先定义字符型变量a的长度为50,然后给a定义一个前后都有空格的非空值。
运行以上代码,结果如图3-22所示。
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00084004.jpg?sign=1738804773-oPWC5sZ6yMMhr06X0wUndLIZCQahweR6-0-d35495fce318da11655787cfad5cba63)
图3-22
length()函数与lengthn函数的返回值都是4,这是因为a的最后一位为空格,两个函数均不考虑;lengthm()函数与lengthc()函数的返回值为50,这是因为在length语句中我们定义了a的长度为50,所以它占用的内存和包含空格的总长度为50。
在使用length相关函数的时候,一定要注意区分它们的异同。
针对字符型变量,SAS中还有大量函数可供我们使用,因为篇幅所限不能一一介绍,表3-10所示为SAS字符型变量常用的函数,在实践中可以先按图索骥,再灵活运用,最后融会贯通。
表3-10
![](https://epubservercos.yuewen.com/36B4B8/21182128501953606/epubprivate/OEBPS/Images/img00085001.jpg?sign=1738804773-4go1QbRgp5zMsFsteWykVfkXfE3lIzTg-0-63756ef2ec1518eacba21cdfdec96c5d)
在本节最后,相信大家心中还有一个问题,那就是数值型和字符型变量是否可以互相转化呢?答案自然是可以,转化的方法也是使用相关函数。不过,在学习相关函数之前,需要了解SAS中的一个概念——数据格式,它是SAS灵活处理和显示数据重要的概念。下一节我们将探讨数据格式。