SAS数据统计分析与编程实践
上QQ阅读APP看书,第一时间看更新

3.3 数据格式

本节探讨一个全新的概念——数据格式。这个概念与上一节学习的数据类型并不相同,甚至可以说是完全不同的概念。

数据格式是SAS存储和理解数据的分类,它可以将同一种类型的变量标记和显示为不同的格式,方便SAS程序和数据分析师理解和进一步操作,也能方便数据审阅查看者更好地理解数据的意义。字符型变量和数值型变量各自有很多预定义格式,我们也可以在SAS程序中自定义数据格式,在不改变变量值的情况下改变数据的显示样式。

如果上一段话让你不知所措,我们先从一个例子看起,打开sashelp库中的cars数据集,选择Invoice变量(变量信息如图3-23所示),如果你使用的是SAS桌面版,请右击该变量选择变量属性;如果你使用的是虚拟机版或SAS Studio,直接点击左侧变量列表中的Invoice即可。

图3-23

在变量列表底部显示的是被选中变量的信息,包括属性、标签、名称等,其中输入格式和输出格式就是我们今日要探讨的数据格式。sashelp.cars数据集中的Invoice变量没有输入格式,只有输出格式,格式为DOLLAR8.,这是SAS自带的一个数据格式,表示以DOLLAR符号开头,总长度为8位的数据格式,图3-24所示便是Invoice显示的格式。

图3-24

一般而言,SAS中数字的显示方式是直接显示,不包含分隔符,而使用DOLLAR格式则可以改变数据的显示样式。

关于数据格式,在正式学习之前,首先明确如下几点。

(1)数据格式既有大量预定义格式,也可以通过程序进行自定义。预定义的格式在SAS启动的时候会自动加载,可以直接使用。自定义格式需要先运行定义程序,才可以使用。在SAS退出后自定义数据格式不会保存。

(2)字符型和数值型数据类型分别对应各种数据格式,而属于两者的格式泾渭分明,也就是说,一种格式要么属于数值型,要么属于字符型。

(3)格式既有输入格式也有输出格式,输入格式用于SAS理解数据,输出格式用于SAS显示数据。

(4)最重要的,数据格式并不是某个值的固有参数,可以在编程过程中修改。数据格式的改变会导致数据的显示样式有所不同,但不会影响值。

明确了以上几点误区,我们来开始正式理解SAS数据格式。

3.3.1 创建、改变和删除数据格式

在上一个例子中,invoice变量的数据格式是已经定义好的,这次让我们自己定义一个数据格式,定义数据格式可以使用format语句:

运行后查看数据集,变量price为$6500,说明format语句给price变量设置格式,格式为dollar8.。仍然必须强调,虽然price的值显示为$6500,但这并不是说format语句将price的值从6500改成了$6500,而只是让price显示为$6500,这里一定要分清,这也是数据格式的基础概念。

除了format,还有attrib语句可以设定数据格式。与format不同的是,attrib语句可以设置变量的各种属性,例如长度、输入格式、输出格式、标签,而format是专门用于输出格式的语句。例如以下代码:

生成的数据集和变量如图3-25所示。

图3-25

可以看到,变量price的标签、长度和输出格式都分别被设定,而显示的样式与之前一例相同,都是$6500。

在SAS程序中,我们还可以修改变量的格式,最简单的方法仍然是使用format语句。在了解之前首先要明确SAS程序的执行顺序。SAS的执行遵循先到先得的运行方式,例如在data步中的语句:

我们分析一下它的运行过程。注意之前说到的,SAS遵循先到先得的运行方式,语句被整体提交后,前面的语句先被运行,即最先被运行的是第一个if语句,此时a和b还没有被赋值,SAS会将a、b都认为是缺失值,a>b不成立,所以变量word没有被赋值。之后a与b分别被赋值,当后一段if语句被执行之后,因为2>1,条件成立,word2被赋值为Effect。

这就是SAS语句运行的过程,那么有没有例外呢?答案是有的。当data步的语句是涉及数据集操作的时候,这些操作无论在哪里都会在最后被执行。什么是涉及数据集的操作呢?简而言之就是改变了数据集中变量的操作,例如keep、rename、drop,它们的运行就是数据集操作,都是对数据集中的变量进行取舍和改名。例如以下代码:

按照之前说的先到先得的原则,先对a赋值,然后保留变量a、b、c,但此时b、c还没有被初始化,按照道理在日志中会显示warning,提示变量b、c在数据集中不存在。但不要忘记,keep是涉及数据集的操作,真正的执行顺序是SAS在执行a变量赋值后,看到keep语句记录并跳过,继续执行下面的b、c赋值,最后再执行keep语句,此时a、b、c均已初始化,所以全部保留。

以上内容不仅是为了帮助读者理解SAS执行的顺序,更是为了引出修改和删除数据格式的话题。数据格式的修改不会涉及数据集,所以仍按照先到先得的原则,例如我们进行如下操作:

再分析一下它的执行过程,首先变量a被赋值,然后变量a被设定输出格式,为dollar8.,下一句继续对a设定输出格式,格式名为8.5,这个格式是SAS常用的数值型变量的小数位的数据格式,我们很快就会讲到。这步执行会覆盖上一步的执行结果,最终变量a的格式被设定为8.5,生成的数据集如图3-26所示。

图3-26

说到这里,相信聪明的读者已经想到删除格式的方法了,仍然使用format语句,对变量设置一个空格式就可以了。例如以下代码:

生成的结果如图3-27所示。

图3-27

我们对变量Invoice设置一个空格式,这样就去除了它的原有格式。读者应该记得在之前的例子中,Invoice的格式为dollar8.,而现在它的输出格式为空,显示样式是纯数字。

最后,我们要介绍一下批量修改变量格式的方法。这里涉及一个新概念——自动变量。自动变量是SAS在运行过程中自动生成的变量,它们不会出现在数据集中,但可以在data步被使用,这里我们要用到的自动变量为_all_,它代指某一步中的所有变量,例如我们把以上的代码稍作修改:

这样运行的结果如图3-28所示。

图3-28

MSRP和Invoice变量的输出格式全部被清除,都显示为普通的数字。因为_all_就是data步生成的代指数据集中所有变量的自动变量,实际上该数据集中的所有变量都被删除了格式,因为MSRP和Invoice最明显,我们一眼就可以看出来。

_all_可以表示某数据集中的全部变量,_numeric_和_character_分别表示数值型变量和字符型变量,这些都是SAS中的自动变量,即SAS在某一步执行时自动创建的变量。自动变量还有很多,掌握并使用它们会大大简化编程的复杂度。

归根结底,数据格式仍然是数据的一部分,不同类型的数据就会需要不同的数据格式,接下来的3.3.2到3.3.5共4个小节我们将对数值型变量格式、字符型变量格式以及自定义数据格式进行翔实的讲解,以及回答上一节结尾提出的问题,即不同类型的变量是否可以相互转换。

3.3.2 数值型变量的格式

为数值型变量赋予格式,可以更好地理解数字意义,面对数字20201224,相信很多读者在脑中的第一反应是把它理解为2020年12月24日,这就是将数字套用到了脑中的数字格式。数字本身是抽象的,但有了格式的数值型变量就会变得有意义。

针对数值型变量,通用的格式为w.d,这是一类数据格式的缩写,表示的是总长度为w,小数点后长度为d的数据格式,例如8.4,表示总长度为8,小数点后有4位的格式,注意这个长度是包括小数点的,但当无法满足长度要求时,数据格式会先保证整数位。例如以下例子:

运行后查看数据集,发现a的显示样式为500.0000,算上小数点,总长度为8位,小数点后为4位,与我们的预期相符,而b的显示样式仍然为5000000,这是因为当总长度不足以显示全部小数点位数时,SAS会优先满足整数位,例如按照小数点后4位的显示样式,b应该显示为5000000.0000,但因为总长度位8,所以截取前8位,为5000000.,因为无法显示小数位,那么小数点省去,结果为5000000。

w.d的数据格式非常灵活,可以自由地修改数据显示的位数,在这里仍需要强调,使用format语句,改变的只是数字的显示样式,而非数值。在工作中,我们往往需要对数据的小数位数进行删减,方便阅读与理解。一般而言,平均值和中位数的小数位数为原数据的小数位数+1,方差为小数位数+2,p-value保留3或4位小数。

除了w.d格式外,SAS还提供了多种多样的数值型变量的格式,例如在之前例子中用到的dollarw.d,它是在数字前加上美元符号$,长度和小数位数仍按w.d格式显示,例如将数字1234.56应用dollar8.3格式,显示结果为$1234.560。

如果你在工作中处理的是欧美公司的数据,很多时候都需要在数字中每隔三位加一个逗号,例如123456写为123,456,这是因为在英语中每逢3位都有一个新词来描述,例如thousand、million、billion、trillion,而中文是每逢4位使用新词来描述,例如万、亿、兆、京,两种不同的语言系统给我们快速理解数字造成了一些困难。在SAS中,可以使用commaw.d格式为数字加上逗号分隔符,例如以下代码:

运行后生成的结果为1,234,567.89,注意这里逗号也会占用长度,如果w.d格式中的w长度不够,则无法显示逗号分隔符。

除了以上内容,SAS还提供了诸如使用科学计数法显示的ew.格式、二进制的binaryw.格式等,下面使用数字12345为例,看看各种格式的显示样式分别是什么样,如表3-11所示。

表3-11

当然,SAS提供的数字格式还有很多,更多内容可以查看SAS官方文档,链接如下:

http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a001263753.htm

3.3.3 字符型变量的格式

相比起数值型变量,字符型变量是我们在数据收集工作中更容易获得的数据,像一个人输入的文本、新闻稿、聊天记录等,都是以文本格式存储的。目前通过数据分析和人工智能,我们正在逐步实现自然语言处理、会议记录转写等功能,这都需要我们对字符型变量的处理功力。SAS同样提供了大量字符型变量的数据格式。下面以“Hello,SAS”这段文本为例,看看不同的格式会把数据转化成什么样子。

有时,我们需要对比字符串的内容,或者对其进行简单的加密,这时一般使用十六进制格式,格式名为hex.,注意SAS的字符型数据格式前方都需要加一个$符号,表示数据类型为字符型,那么使用$hex100.格式得到的结果为:

这是将字符的ASCII码值转化为十六进制后的结果。

也可以选择将字符串转化为八进制,格式与数值型变量的转化方式相同,为octal100.,得到的结果为:

通过使用reverj.格式,可以让文本内容反过来,例如应用reverj10.格式,得到的结果为:

所有字符都按反向顺序输出并且保留了空格。

在处理英文文本时,有时我们希望排除大小写的影响,需要把所有文本都转化为大写,使用upcase10.格式可以完成操作,获得的结果为“HELLO,SAS”。

这里仍然要提醒两点:

(1)以上案例中虽然使用了“转化”的描述,但读者一定要明确,使用format改变数据格式,并不会改变数据中的值,这也是笔者反复强调避免误解的地方。

(2)各种数据格式后面所跟随的数字表示该格式的总长度,在设置时需要合理分配,避免长度太短无法显示出完整的文本或数字,也不可太长。

3.3.4 自定义数据格式

以上介绍的字符型与数值型变量的格式,都是SAS系统自带的,开启SAS时自动加载的格式,那么我们是否可以自定义数据格式呢?

例如我们有如图3-29所示的数据集,记录了每名学生考试的成绩,包括语文、数学和英语3门,它们以数值型变量的格式存储,为了更直观地看出每名学生成绩的好坏,可以将成绩分为4个等级,90分及以上为A,80分及以上为B,70分及以上为C,70分以下为D。我们不希望为数据集添加新的变量,只希望改变数字显示样式,这时应该怎么办呢?

图3-29

SAS中并没有数字格式把数字转化为ABCD显示方式,但SAS提供了自定义的数据格式,这就是proc format,这是一个proc步骤,用途为创建自定义变量格式,其语法为:

例如我们使用如下代码:

运行以上语句后,生成了一个名为score的数值型格式,针对不同的数字显示为不同的样式,然后再使用format语句在data步中转换Chinese、Math和English三个变量,获得的数据集如图3-30所示。

图3-30

可以看到数据集变成了以ABCD显示的样式,我们不用看具体的分数,只需要看成绩所对应的档位就可以大致了解一名学生的成绩。例如8号同学,它的语文和数学成绩非常优秀,但英语成绩偏低,说明对其进行针对性的英语辅导可以很好地提升他的总成绩,再如1号同学,三科成绩均为D,看来需要全面补习了。

需要注意的是,如果生成的是数值型变量格式,在value后可以直接连接格式名称,如果是字符型变量,那么需要按照SAS的习惯,在格式名前加上$符号。

另外,proc format中可以使用value或invalue,value创建的是结果为字符型的数据格式,而invalue创建的为数值型格式。

3.3.5 字符型变量与数值型变量的转换

数据类型在SAS中所占的篇幅并不是很大,为什么笔者要花一整节来讲解呢?其实原因就是为了引出两种变量类型转化的话题。在SAS中,字符型变量和数值型变量是泾渭分明的两种类型,但实际工作中我们经常遇到两种变量类型混合存储的方式,例如将学生的成绩输入为字符型变量,或者用数字表示临床试验中患者的ID号,此时我们需要一种手段将变量转换再进行操作,这时就要请出SAS中的两个重要函数:input和put。

1.字符型变量转化为数值型变量

input函数用于将字符型变量转化为数值型,它的语法为:

注意:此处的数据格式的意义为以字符型存储的数值是按照什么格式存储的。

我们还是举例说明,例如我们有图3-31所示的数据集,变量value存储的虽然都为数字,但都是字符型格式。

使用input函数创建新变量value_n,让其值变为数值型,具体语句如下:

生成的结果如图3-32所示。

图3-31

图3-32

看起来value与value_n的数值没有区别,但通过查验两者的变量类型可以发现,value为字符型和value_n为数值型。更简单地,我们发现value的值为左对齐而value_n的值为右对齐,在SAS中,字符型变量为左对齐而数值型变量为右对齐,所以我们一眼就能看出两个变量的类型有所区别。

更多时候,在数据收集过程中获得的值带有小数点,例如图3-33所示的数据集为某患者每周吃药的情况,其中week表示周数,dose表示药物服用总量。

图3-33

可以看到,每周记录的药物总量的小数位数均不同,此时如果进行类型转化,很难选定合适的数据格式,如果选择形如8.的格式,则会将小数点后面的部分省去,无法获得精确值,如果满足小数点最长的位数,则其他数据会被自动补0,有没有一种数据格式可以完美地理解数据所表达的意思呢?

答案当然是有的,这就是被誉为万能数据格式的best.,它是让SAS自动理解数据的格式,选择每条记录最合适的格式进行转化。例如以下代码:

运行得到的结果如图3-34所示。

图3-34

在生成的结果中,dose_n即为数值型变量,它按照字符型变量中的最大位小数位数转化为数值型变量,每一位小数点都得到了保留。这就是万能格式best.的功劳。

2.数值型变量转化为字符型变量

下面说说数值型变量转化为字符型变量的操作,使用的是函数put,它的语法与input相同:

这里的数据格式是转化之后希望SAS显示的数据格式名称。

例如数据集put中有变量value=1314,如果我们希望把其按照原样转化为数值型,可以使用如下代码:

如果我们希望转化结果产生两位小数,那么可以按照“7.2”的格式转化。

有时,我们会碰到和input最后一例相同的问题——数据的长度不同,聪明的你一定想到了,也可以使用best.格式进行转化,让我们先看看实际效果。有如图3-35所示的数据集,包含的是某个临床试验患者体重测量的结果,其中PatientID表示患者,weight表示体重值。

图3-35

现在我们希望生成新变量weight_c,以字符型变量存储weight中的数值,使用以下程序:

运行结果如图3-36所示。

图3-36

虽然生成了weight_c变量,但好像与我们的预期有所出入。理论上字符型变量为左对齐,但变量weight_c的值前出现了或多或少的空格。这是因为best.格式虽然可以选择最合适的数据格式,却无法为每一个值都安排最合适的格式,因为不同值的总长度不同,best.只好选择一个默认的值作为总长度,在SAS默认设定中这个长度为12,因此字符变量的前半部分就被空格填充,保证每个值的总长度为12。这是我们不希望看到的结果,应当如何解决呢?

相信大家还记得前面学过的字符型变量对应的函数,可以使用strip函数删除字符串前后的变量,将以上代码改为:

运行后生成的结果就正常了。考察修改的语句,可以发现是在put函数外层套了一个strip函数,将put函数的结果作为strip函数的参数,这种操作称为函数的嵌套,是SAS完成复杂操作的常用手段。

本节介绍了SAS中的数据格式,内容比较庞杂,着重记忆固然重要,更需要的是在工作与学习中灵活使用。这两节介绍了两种变量类型和它们对应的数据格式,相信有经验的读者心中会产生一个疑问:在日常生活中我们收集的数据很大一部分都是日期和时间,这两种变量在SAS中是如何存储的?是一种全新的变量类型吗?下一节我们将探讨日期和时间的数据格式。