SAS 程序是一个庞杂的体系,无论是从原始数据的整理、统计方法的实现,还是统计结果的呈现来说,了解其背后术语和概念、理解其运行原理与机制,对阅读理解 SAS 程序,自己编写 SAS 程序,快速实现数据整理和统计分析需求都将大有裨益。因此,本章我们就来扒一扒那些重要的,尤其是被市面上的 SAS 书籍所忽略的基本概念和基础知识。
正如上一章所提及的,我们大部分人首先接触的、日常使用的都是 Windows 平台下的 SAS 视窗管理系统 DMS。我们通过 DMS 与 Foundation SAS 进行交互,从而完成我们的工作。
在 Windows 下 可 以 看 到 SASHOME 安 装 目 录 下 有 SASFoundation 文 件 夹, 里 面包含了诸如 ACCESS、 BASE、 GRAPH 等诸多组件。正如第一章所提及的,其实整个Foundation 就是由 Base SAS、数据管理与访问、用户界面、报告与绘图、分析、可视化与发现、商业解决方案、应用开发以及网络应用等组件构成的(见图 2-1)。
Foundation SAS | ||
---|---|---|
报告与绘图 | 数据访问与管理 | 用户界面 |
分析 | Base SAS | 应用开发 |
可视化与发现 | 商业解决方案 | 网络应用 |
概括而言, Foundation SAS 提供了以下功能:
- 管理SAS任务的图形用户界面,如DMS、 EG、 SAS Studio等
- 高度灵活、可扩展的编程语言,即SAS语言
- 丰富的内置SAS过程
- Windows、 UNIX以及z/OS(OS/390)的多平台运行
- 几乎任何数据源的访问,如DB2、 Oracle、 SYBASE、 Teradata、 SAP以及微软的Excel
- 几乎所有主流的字符编码
Base SAS 是 Foundation SAS 的核心,是运行 SAS 必备模块,由 DATA 步、 PROC 步、MACRO、 DATA 步调试器、 ODS 以及 SAS 窗口环境组成。* DATA步:是由用于操作管理数据的编程语言组成的, SAS 编程优势的集中体现之一就是DATA步编程。
- PROC步(SAS Procedures):是数据处理、统计分析与结果呈现的工具。 BASESAS里的SAS 过程有限,欲完成特定的处理和任务,需要加载特定模块,如SAS/STAT、 SAS/ETS、 SAS/IML等。
- 宏(Macro Facility):宏的本质是文本替换,它可扩展和定制SAS 程序,完成重复、复杂的任务。
- DATA步调试器:追踪DATA步执行情况,便于查找DATA步的运行错误。
- 输出传递系统(Output Delivery System, ODS):将SAS输出以易访问的格式输出,如列表输出(LISTING)、 HTML输出、富文本输出(RTF)、 PDF输出以及以数据集形式输出等。
- SAS窗口环境:开发测试SAS程序的环境,最为广泛的是SAS视窗管理系统(DMS)。
DATA 步、 PROC 步和 MACRO 是 SAS 程序的三大核心。通常我们说写 SAS 程序就是:在 DMS 的高级程序编辑器里编写 SAS 的 DATA 步、 PROC 步以及宏。不过,在写 SAS程序之前,我们先熟悉下 SAS 处理数据的流程。
如图 2-2 所示,在数据处理流程上,同 R 软件把所有数据都存在内存里进行计算不同SAS 读入各种来源的数据(可能是外部的原始数据,也可能是 SAS 可以直接打开的 SAS 数据文件),将其存储在硬盘的 SAS 数据集里,经过进一步的整理、清洗,把数据变成可以直接套用统计模型的数据集,然后运行统计模型、跑出统计分析结果、把结果进一步整理成表格、图片甚至是图文混排的报告,还可以把结果抓取出来另存为数据集,便于后续处理。
这个整理数据的过程通常是使用 DATA 步来完成的,分析数据的过程通常是使用 PROC步来完成的,此即人们口中常说的 SAS 两步编程: DATA 步整理数据、 PROC 步分析数据。
当然,在实际导入、整理数据的过程中,也不仅仅只限于用 DATA 步,巧妙运用PROC 步往往能事半功倍。同样,在 PROC 步做完分析后,也需配合用 DATA 步做进一步的结果整理,以便输出更易读的结果报告.
SAS 从导入数据到完成统计分析报告,这中间涉及很多重要的基本概念,如数据集、DATA 步, PROC 步等。接下来,我们就顺着这个流程,把最基本、最重要的概念捋一遍,那就从逻辑库和数据集开始吧。
从上一小节 SAS 数据分析流程里我们知道,要进行数据分析,需要我们先把外部的Windows 数据文件,比如 CSV 文件转成 SAS 可以直接识别和处理的 SAS 数据集,而 SAS 数据集则必须置于 SAS逻辑库(SAS Library)中。数据存储在数据集里,数据集存放在逻辑库中,数据、数据集和逻辑库的关系就如同数据页、文件夹和抽屉的关系(见图 2-3)。
在 Windows 环境下, SAS 逻辑库其实是映射到一个(当然,也可以是多个)文件夹的名字。 SAS 会按照某些约定的格式去读写 SAS 逻辑库中的 SAS 数据集,这些约定的格式,被称为引擎(Engine),如 SAS 9.4 默认的引擎就是 V9, SAS 9.4 会用 V9 这种格式去生成 SAS 数据集、读取数据集。此外, SAS 还可以用其他引擎读取外部数据,如可以 25用 XLSX 引擎读取 Excel 文件, SPSS 引擎读取 SPSS 数据等(见图 2-4)。
大多数情况下,我们都希望处理好的 SAS 数据集能够保存在某个文件夹下,以备后用,而对于一些中间数据或者临时数据,我们则希望关掉 SAS 后他们就被自动删除。因此,SAS 给我们分别提供了永久逻辑库和临时逻辑库(见图 2-5)。永久库除了有 SAS 自带的Maps、 Mapsgfk、 Mapssas、 Sashelp 以及 Sasuser 外,我们也可以自建存放自己数据集的永久库,而临时库在 SAS 里就一个,名为 Work 库。我们在 Work 库里倒腾数据时很可能产生了一大堆中间数据集,最后把倒腾好的最终数据集存入永久库即可,关掉 SAS 软件后,那些留在 Work 库里的中间数据集不用我们去操心,会被自动删除。
例如,我们想在 D:\03 Writting\01 SAS 编程演义 \02 Data\Clean 的位置建立一个永久逻辑库,取名为“Demo”。一种方法是采用工具按钮如图 2-4 所示方法生成;另一种方法则是通过 LIBNAME 语句生成。基本格式就是: LIBANME 数据库名称 “数据库物理地址”,具体可见程序 2-1 利用 LIBNAME 语句自建永久逻辑库。
程序2- 1 利用LIBNAME语句自建永久逻辑库
*===带*号的行是注释行===;
*===自建永久库===;
*===取名Demo,地址: D:\03 Writting\01 SAS编程演义\02 Data\Clean;
libname demo "D:\03 Writting\01 SAS编程演义\02 Data\Clean";
需要留意的是,通过工具按钮建立永久库时,可以在建立时勾上“Enable at startup”,下次启动时 SAS 就会自动加载这个永久库,而通过 LIBANME 语句自建的永久库在 SAS重新启动时,需要重新运行 LIBNAME 语句才可以重新把永久库的名字和物理地址关联上,然后才能在 SAS Libraries 里看到。如果想要让 SAS 语句建立的永久库在启动后就能看到,可以把包含 LIBNAME 语句的程序命名为autoexec.sas,并放入和 sas.exe 同级的目录中,此后每次 SAS 启动时会自动运行 autoexec.sas 程序,建立永久逻辑库。
SAS 数据集有两种:一种是 SAS 数据文件 (SAS Data File);另一种是 SAS 视图 (SAS View)。数据文件和视图用 SAS 打开后都类似于一张表格,不同的是数据文件是真实存在的数据,而视图是运行查询语句后动态生成的数据,两者的图标在 DMS 中也不一样,如图 2-6 所示。不过两者在实际内容效果方面却是一样的,如图 2-7 所示。由此而带来的疑惑是:既然两者的实际内容效果一样,有了数据文件为何还需要视图?谨记:视图是依据查询语句动态生成,因此视图本身几乎是不占存储空间的,利用视图可以节约硬盘空间。
SAS 数据集的组成成分,在逻辑上可能仅有描述信息(数据视图时的情况),也可能是包含描述信息、数据值(数据文件时的情况)以及可能的索引和其他扩展属性四部分,具体见表 2-1。单就数据集的描述信息来说,包括两部分:一部分是数据集概要信息,如数据集类型、创建引擎、创建时间等;另一部分是变量信息,如变量名称、类型、长度以及格式等。至于数据值,也即如图 2-7 所示的内容,是由行、列组成的表。行和列在 SAS数据集里分别称为观测(observation)和变量(variable),这部分内容是数据文件独有的,SAS 视图只有打开运行时才生成。
如果希望查看数据集的描述信息,特别是如图 2-8 所示的第三部分内容:变量列表及 29其属性,则可以通过 PROC CONTENTS 实现,并且可以进一步加工整理成数据库的变量字典,极大地方便后续的工作。当然,也可以通过右击数据集,通过「查看列」来查看变量信息(见图 2-9)。
欲查看数据值,可以通过 PROC PRINT 实现,当然,双击数据集也可以。不过在实际操作中,我们不会总是这样查看所有数据值,而是希望通过统计过程查看一些统计信息,如均数、分位数、分布图等。 由于数据集都在逻辑库下,因此在程序中指定数据集时,需要按照「逻辑库 . 数据集」的二级命名格式来明确告知是哪个逻辑库下的哪个数据集,中间用英文的句号隔开,当逻辑库为临时库 WORK 时,可以省略掉一级命名结构和句号「逻辑库 .」。
查看数据集的信息是学会了,但是如何创建数据文件呢?回顾图 2-2,我们知道有两种方式:导入外部数据或者读取既有的 SAS 数据集。导入外部数据,我们既可以用 DATA步,也可以用 PROC 步,具体情况将在第 3 章做详细介绍。读取既有数据集,我们可以用 31DATA 步的 SET 语句,这里我们简单举例,把 SAShelp 库的 Class 数据集读取到我们自建的永久库 Demo 里和临时库 WORK 里,并都命名为 class_datafile。
程序2-3 SET语句建立数据文件
*===自建永久库;
libname demo "D:\03 Writting\01 SAS编程演义\02 Data\Clean";
*===建永久数据集, demo.不可省略;
data demo.class_datafile;
set sashelp.class;
run;
*===建临时数据集, work.被省略;
data class_datafile;
set sashelp.class;
run;
建立数据文件学会了,如何建立视图呢?有两种方法: DATA 步的 VIEW 选项和 PROC SQL 的 Create view 语句。
程序2-4 创建SAS视图
*===建视图;
*===from data setp;
data demo.class_view/view=demo.class_view;
set sashelp.class;
run;
*===from Proc sql;
proc sql;
create view demo.class_view as
select *
from sashelp.class;
quit;
数据集中最为重要的一个概念莫过于变量(variable)。在 SAS 里,我们可以将变量简单理解为存储数字或者字符的容器,一个变量就是一列。变量有其属性,包括名称、类型、长度、输入格式、输出格式、标签、观测中的位置以及索引类型等。图 2-9 展示了变量的属性信息。
变量名有其命名规则,后面会详细介绍。同其他编程语言或者统计软件不同的是,SAS 的变量类型非常简约,只有两种:数字和字符。数字型变量存储浮点数,包括日期和时间(在 SAS 里,日期实际存储的是距离 1960 年 1 月 1 日的天数,而时间实际存储的是距离凌晨的秒数,具体可见程序 2-5);字符型变量存储的是拉丁字母、 0 ~ 9 阿拉伯字母以及其他特殊字符,默认长度是 8 个字节。输入 / 输出格式是 SAS 读取或者显示变量的规则,数字型变量默认输入格式是「w.d」,输出格式是「BEST12.」;字符型默认输入、输出格式均为「$w.」。关于格式,我们将在第 7 章做详细介绍。
程序2-5 SAS日期、时间以及日期时间的本质
data tmp;
date="01Jan1960"d;
time="00:00:00"t;
datetime="01Jan1960 00:00:00"dt;
run;
前面几个小节我们基本上都把 SAS 当作一个软件来进行介绍,辅助性地展示了一些SAS 代码,对于初学者,如果没看懂前面的代码没有关系,理解软件层面的概念即可。从这一节开始,我们一起捋一捋 SAS 作为一门编程语言的基本概念和基础知识。
SAS 程序是由一系列 SAS 语句(statement)组成,所谓 SAS 语句通常是指以 SAS 关键字(keyword)开头,始终以分号(;)结束的代码行。最常见的 SAS 关键字就是「DATA」和「PROC」,因此最常见的语句就是 DATA 语句和 PROC 语句。当然, SAS 的关键字多如牛毛,我们也不必刻意去死记硬背每一个 SAS 关键字。在 DMS、 EG 和 SAS Studio 的编辑器中, SAS 都会自动给关键字着成深蓝或者蓝色, EG 和 SAS Studio 还会给出提示,初学者可以尝试看看。
另外,如果从程序块上来讲解, SAS 程序可以分为两大块: DATA 步和 PROC 步。所谓一个「步」(step)是指这样的一个程序块。
- 以DATA语句或者PROC语句开头。
- 以RUN语句(大多数情况下)、 QUIT语句(部分情况下)、新的DATA语句或者PROC语句结束。
在 SAS 编辑器中, SAS 会自动显示横线以隔开 DATA 步或者 PROC 步(见图 2-10)。需要留意的是,有些语句只能在 DATA 步里出现(如 INPUT 语句),有些语句只能在 PROC 步里出现(如 CLASS 语句),有些语句 DATA 步、 PROC 步都可以出现(如FORMAT 语句),而还有些语句可以既不在 DATA 步也不在 PROC 步出现,他们可以单独出现(如前面使用过的 LIBNAME 语句),此即 DATA 步语句、 PROC 步语句及全局语句的概念。
SAS 程序除了单独的 DATA 步和 PROC 步程序,还有可以把他们打包组合在一起的程序,那就是宏程序,宏程序本质上是文本替代,用更少的文本替代更多的文本。这个话题暂且不做过多介绍,留在后面的第 10 章进行详细说明。
规则的 SAS 程序书写风格看起来基本就是被 DATA 步和 PROC 步分割的条块,其实SAS 程序书写的格式是比较自由的,如果要真正究其语法规则的话,有两方面:① SAS语句语法规则;② SAS 名语法规则。
SAS 语句语法规则:
- 分隔单词的可以是一个空格或特殊字符(比如加号、等号等运算符),也可以是 多个。
- 程序可以在任何列开始,也可以在任何列结束。
- 单个语句可以写在多行,多个语句也可以写在一行。
SAS名是指 SAS给其一些语言元素(如逻辑库、数据集、变量以及格式等)的名称标记。SAS 名有两类。
(1) SAS 系统定义名,如自带的库名 WORK、 SASHELP 等;如特殊的数据集名 NULL (不创建数据集)、 DATA(自动数据集名)、 LAST(最后一个活动数据集);如 SAS DATA 步的自动变量名 N (观测号)、 ERROR(错误标识变量);如特殊的变量列表名 CHARACTER(所有字符型变量)、 NUMERIC(所有数字型变量)、 ALL(所有变量);以及 SYS 开头的宏变量名如SYSDATE(日期)、 SYSVER(SAS 版本)等。 (2)用户自定义名,自定义名不能与系统定义名相冲突,且需符合 SAS 命名的语法规则,总结起来可归纳为以下三点。
- 只能由数字、字母、下划线组成。
- 首字符不能是数字。
- 长度限制各有不同,有的最长可以达32个字符(如变量名,宏变量名),有的最长只能有8个字符(如逻辑库、文件引用名以及引擎名)。
这个命名规则一定要遵守吗?是的,都应该遵守。这个规则能打破吗?可以,但不推荐。不过,有的时候,我们也确实有特殊需求:比如如何打破规则让 SAS 也可以用中文命名数据集、命名变量呢?这时候,我们可以通过修改系统选项 VALIDMEMNAME 和VALIDVARNAME 的值来实现,如图 2-11 所示.
程序2-6 SAS中文名数据集和变量名
*===中文名数据集;
*===中文名变量;
options validmemname=extend validvarname=any;
data 中文名演示;
SAS中文变量名="YES";
SAS中文变量名="YES";
'2SAS中文变量名'n="YES";
'2SAS中文变量名'n="YES";
'SAS空 格变量名'n="YES";
'SAS空# @ %格特殊字符变量名'n="YES";
run;
语法规则只是对编程的合法性给出了最低的要求。在合法性的基础上,我们还应追求语法风格的统一和规范,这样不仅方便自己日后阅读调试,也方便他人审阅,下面是同一段简单的 SAS 程序,对比左右两边的风格,正常的人类都更愿意看左边的,对吧?编程人士中有一个术语叫 Good Programming Practice, GPP,即良好编程实践,很多编程语言都有推荐的编程规范,遵循这些规范,可以极大地方便与同行的交流,笔者自己总结过一些 SAS 的编程规范,具体可参考附录。
程序2-7 编程风格:规范与凌乱
*===自建永久库;
libname demo "D:\03 Writting\01
SAS编程演义\02 Data\Clean";
*===建永久数据集, demo.不可省略;
data demo.class_datafile;
set sashelp.class;
run;
*===建临时数据集, work.可以省略;
data class_datafile;
set sashelp.class;
run;
*===凌乱风格;
libname demo "D:\03 Writting\01
SAS编程演义\
02 Data\Clean";
data demo.class_datafile;
set sashelp.class;
run; data class_datafile;
set sashelp.class;run;
作为一门编程语言, SAS 语言元素除了上面提及的 SAS 语句(statements),还有表达式(expressions)、选项(options)、格式(format)、函数(function)以及 Call 列程(Call Rountine)等.
- 表达式 表达式是 SAS 语言中一个非常重要的概念, SAS 在生成一个新变量、给一个变量赋值、计算新值、变量转换以及依据不同的条件进行处理都需要借助表达式来实现。什么是表达式? SAS 官方给表达式的定义比较拗口:表达式是由一系列操作数和操作符构成的、可执行的、并且产生结果值的序列。简单来说,表达式就是告诉 SAS 对什么对象执行什么操作,从而得到一个结果的命令。被操作的对象叫操作数(operands),执行操作用的符号就是操作符(operators),习惯上称运算符的更多,执行的结果可能是一个数字值,也可能是一个字符值,还可能是一个布尔值(是 / 否、真 / 假、 1/0)。 (1)操作数:操作数可以是常量、变量,也可以是表达式。常量,顾名思义,表示一个值是恒常固定的量;同理,变量表示值是可以变化的,有一套数值去刻画某个特征的量。
常量有以下四种情况。
- 字符常量:字符常量由1~32767个字符组成,必需放在英文引号内,引号可以是单引号,也可以是双引号。字符常量中包含单引号(双引号)时,可以用双引号(单引号),或者连续的单引号(双引号),如:“Hongqiu Gu’s Book”。
- 数字常量:数字常量无须多言,只需留意除了标准计数法(如: 1, -5, +49,1.23, 01),科学计数法(如: 2E23, 0.5e-10)和十六制计数法(如: 0C1X、9X)也可以。
- 日期时间常量:时间日期常量包括日期、时间、日期时间常量三种,命名是需要采用单引号或双引号加D(日期)、 T(时间)、 DT(日期时间)后缀来分别表示,如‘08Sep2016’D、 ’11:11’T、 ’08Sep201611:11’DT,具体可参考程序 25 SAS日期、时间以及日期时间的本质,这种引号加字母后缀的命名方式称之为名称文字(Name Literal),在使用非规范的数据集名、变量名时也需要用到这种形式。
- 位 测 试 常 量 : 在 引 号 里 由 0 , 1 以 及 点 (. ) 组 成 字 符 串 , 且 后 缀 为 B ,如’..1.0000’b,用来测试对应的位是否为0或1。这种常量使用较少,在此不做具体介绍。
变量有两种类型:字符变量和数字变量。日期、时间以及日期时间在 SAS 里其实也是以数字存储的数字变量。如前所述,日期变量的值为距离 1960 年 1 月 1 日的天数,时间变量的值为距离凌晨的秒数,日期时间的值为距离 1960 年 1 月 1 日凌晨的秒数。
程序2-8 SAS中的常量
*===常量;
data _null_;
*==字符常量;
c1="Hongqiu Gu's Book";
c2='Hongqiu Gu''s Book';
c3='Hongqiu Gu"s Book';
c4="Hongqiu Gu""s Book";
*==数字常量;
n1=123;
n2=-123;
n3=+123;
n4=1.23;
n5=0123;
*===日期时间常量;
d='08Sep2016'D;
t='11:11'T;
dt='08Sep2016:11:11'DT;
*===在日志中输出;
put c1-c4 ;
put n1-n5 ;
put d yymmdd10.;
put t time.;
put dt datetime.;
run;
(2)运算符: SAS运算符从位置上讲,放在操作数前面的叫前缀运算符(如 +、 -),放在操作数中间的叫中缀运算符(大多数运算都是);从功能上讲,有用于算术运算的算术运算符(如 +、 -、 *、 /),用于比较大小的比较运算符(如 >、 <、 =、 ^=),用于逻辑运算的逻辑运算符(如 ^、 &、 |);算术运算符运算的结果通常为数值,比较和逻辑运算符运算的结果真(1)或假(0)。关于这几种运算符,没有太多可说的,请参考下面的表 2-2、表 2-3及表 2-4。
表 2-2 算术运算符
符 号 | 定 义 | 例 子 | 结 果 |
---|---|---|---|
** | 指数 | a**3 | |
* | 乘 | 2*y | 2 乘以 y |
/ | 除 | Var/5 | Var 除以 5 |
+ | 加 | Num+3 | Num 加 3 |
- | 减 | Sales-Discount | Sales 减去 Discount |
注:乘法中, * 号是必需的, 2y 或者 2(y) 都是非法的。
表 2-3 比较运算符
符 号 | 等 效 字 符 | 定 义 | 例 子 |
---|---|---|---|
= | EQ | 等于 | A=3 |
^=、¬=、 ~= | NE | 不等于 | A^=3, A¬=3, A~=3 |
> | GT | 大于 | Num>8 |
< | lT | 小于 | Num<8 |
>= | GE | 大于等于 | Sales>=100 |
<= | LE | 小于等于 | Sales<=100 |
IN | 等于列表中的一个元素 | Num in (3,4,5) |
注: *NE 的符号在不同的键盘上可能会有所不同。 **>=、 <= 与以前 SAS 版本兼容。 WEHRE 或 SQL 语句中不支持
表 2-4 逻辑运算符
符号 | 等效字符 | 例子 | 运算符说明 |
---|---|---|---|
& | AND | (a>b & c>d) | 两边都为真,运算结果为真 |
|、!、 ¦ | OR* | (a>b or c>d) | 任一边为真,运算结果为真 |
¬、 ^、 ~ | NOT* | Not(a>b) | 取反面结果 |
注: * 不同的操作环境可能符号有所不同。
除此之外,还有取小运算符(><)、取大运算符(<>)以及连接运算符(||)。 >< 和<> 分别用来找到两个操作数中的最小值、最大值, || 用来连接前后两字符。如果只是单个运算符时,不会牵涉到运算顺序的问题,但是,当有多个运算符时,就需要理清运算顺序了,如复合表达式中会有多个运算符,其运算顺序的原则是: (1)先算括号中的表达式,再算括号外。 (2)不同组有不同的优先级。 (3)同组内有不同的运算顺序。 具体示例详见表 2-5。
表 2-5 复合表达式运算顺序
优先级 | 运算顺序 | 符 号 | 例 子 |
---|---|---|---|
组 1 | 从右到左 | ** | y=a**2; |
+ | y=+(a*b); | ||
- | z=-(a+b); | ||
^¬ ~ | if not z then put x; | ||
>< | x=(a><b); | ||
<> | x=(a<>b); | ||
组 2 | 从左到右 | * | c=a*b; |
/ | f=g/h; | ||
组 3 | 从左到右 | + | c=a+b; |
- | f=g-h; | ||
组 4 | 从左到右 | ||
组 5 | 从左到右 | < | if x<y then c=5; |
<= | if x le y then a=0; | ||
= | if y eq (x+a) then output; | ||
¬= | if x ne z then output; | ||
>= | if y>=a then output; | ||
> | if z>a then output; | ||
if state in (‘NY’,’NJ’,’PA’) then region=’NE’;y = x in (1:10); | |||
组 6 | 从左到右 | & | if a=b & c=d then x=1; |
组 7 | 从左到右 | | ¦ ! | if y=2 or x=3 then a=d; |
-
选项 SAS 选项包括系统选项和数据集选项。系统选项主要是一些可以影响整个 SAS 程序执行或 SAS 会话交互的指令,数据集选项是仅用于数据集的选项,如变量的重命名与筛选、观测筛选、数据集权限控制等。
-
格式 格式依据应用场景,分为输入格式和输出格式;依据定义方式,分为系统格式和自定义格式。格式告诉 SAS 按一定的模式读取、显示数据。关于格式,详见第 7 章。
-
函数与 CALL 例程 SAS 函数可以接收参数,执行一些运算和操作,然后返回一个值。 CALL 例程与 SAS 函数类似,不过不能用在赋值语句或表达式中。关于函数和CALL例程,详细讨论将在第6章进行。
我们通过一个综合的例子来简单感受下上面提及的一些概念。
程序2-9 SAS语言元素演示
*====概念演示;
data test2;
length ID $ 4;
input Name $ start yymmdd10. end date8. grade; *输入格式;
FirstName=substr(Name,1,1); *函数substr;
GivenName=substr(Name,length(Name)-1,2); *函数substr;
call cats(ID,FirstName, GivenName); *CALL CATS例程;
if grade>=2 and start<'01Jun2016'd then pay=(end-start)*150;
*比较、逻辑、算术运算;
else pay=(end-start)*100;
datalines;
ZhangXL 2016/08/09 06SEP16 1
WangSJ 2016/07/03 09SEP16 2
WenTC 2016/05/05 02SEP16 3
LiWC 2016/04/09 10SEP16 2
;
run;
options nodate; *系统选项;
proc print data=test2(obs=2); *数据集选项;
var ID start end pay ;
format start yymmdd10. end yymmdd10.; *输出格式;
run;
就如人生中面临的三种情境一样:按照既定的步骤去做一些事情、依据不同情境选择性地应对一些事情、在某些情境下重复做相同的事情,几乎所有的编程语言都设计了三种程序逻辑结构:顺序、选择和循环。
-
顺序结构(sequence) 顺序结构的程序执行时就按照代码出现的顺序依次执行:第一条语句,第二条语句,第三条语句……前面的所有 SAS 代码几乎都是顺序结构式的。
-
选择结构(selection) 最经典的选择结构语句就是 IF-ELSE/THEN 语句,告诉 SAS 在满足某条件的情况下执行一套操作,不满足则执行另一套操作。例如,我们对 SASHLEP 库 CLASS 数据集的人按男女性别的不同分别抓出来放到 Male 和 Femal 数据集。
程序2-10 IF-ELSE/THEN示例
data male female;
set sashelp.class;
if sex="M" then output male;
else if sex="F" then output female;
else put "Invalid sex :" sex ;
run;
需要留意的是:
- 对于情境的分类,要考虑完全。因此,尽量最后加一个ELSE语句,纳入其他所有可能情况。
- 如果某种情境下,希望执行的不仅仅是一个动作,而是多个动作,此时可以在关键词THEN后面用夹板语句DO-END,把多个动作整合在DO-END语句中。例如,我们嫌弃SEX不文雅,把它换成GENDER,用Male、 Female标明男性、女性。
程序2-11 IF-ELSE配合DO-END
data male female;
set sashelp.class;
if sex="M" then do; gender="Male "; output male; end;
else if sex="F" then do; gender="Female"; output female; end;
else put "Invalid sex :" sex ;
run;
- 循环结构(iteration) 循环结构的程序是只要满足某个特定的条件,就重复进行某些操作。 SAS 里常见的循环语句有三种: DO 循环语句、 DO-WHILE 语句以及 DO-UNTIL 语句。 (1) DO 循环语句。 DO 循环语句其实就是 DO-END 语句的衍生,在 DO 后面添加循环的条件,这个条件可以是数字、字符、日期的列表;可以指定起始值和终止值以及步长;还可以是前面两者的混合。 程序2-12 DO循环语句
data schedule;
do date='01Sep2016'd to '30Sep2016'd ; *日期循环;
day=weekday(date);
if day in (1,7) then Activity="Running";
else if day in (2,4,6) then Activity="Writing";
else Activity="Reading";
output;
end;
run;
data random;
do i=1 to 10; *数字10次循环;
r=rannor(23); *生成随机数;
output;
end;
run;
(2) DO-WHILE 语句。与 DO 循环语句每次按照指示变量的值去执行不同, DOWHILE 语句会先判断是否满足条件,如果满足则执行否则跳出循环。 (3) DO-UNTIL 语句。与 DO-WHILE 语句会先判断是否满足条件不同, DO-UNITL语句不管三七二十一,先执行了本次循环再说,而后再判断条件是否满足。在做条件判断时,DO-UNTIL 与 DO WHILE 的思维也不一样: DO-UNIL 是如果不满足,则继续下一次循环,如果满足,则跳出循环。具体可留意程序 2-13 的条件差异。
程序2-13 循环语句DO WHILE 与DO UNTI
data dowhile;
i=0;
do while(i<5);
i+1;
output;
end;
run;
data dountil;
i=0;
do until(i>=5);
i+1;
output;
end;
run;
如果读了上面的文字和程序,对三种逻辑结构还是不太清楚的话,图 2-12 或许能让我们的思维更清晰些。
SAS 编程语言不像其他语言那样有丰富的结构体(struct),用来聚合数据类型,这正如 SAS 的数据类型只有简单的字符和数字两种。不过,其他编程语言的数组(array)的思想倒是在 SAS 编程语言中有充分的利用。
SAS 编程语言里,数组是一系列有特定顺序的变量组成的一个临时变量组。之所以说是临时的,是因为数组仅仅存在于 DATA 步执行的过程中。数组中的变量必须有相同的数据类型,如果全为字符型,则为字符型数组;如果全为数字型,则为数字型数组。此外,如果数组里的值只在一个维度上排列,比如就一行,这就是一维数组;如果数组里的值在多个维度上排列,比如行列上都有,就像一张 EXCEL 表格,这便是二维数组。
在什么场合下会用到数组呢?怎样理解一维和二维数组呢?举例说明:比如某研究项目持续每天测量患者的收缩压(SBP)、舒张压(DBP),并持续了一周,这样就有 7 次收缩压和 7 次舒张压的测量值。当然,我们可以把他们分别存储在 SBP1SBP7、DBP1DBP7 这 14 个变量中。但是仅仅这样,可能还不够,如果后期我们发现这批血压仪的测量值有系统偏差, SBP 比正常测值低 5mmHg, DBP 比正常测值低 3mmHg。现在要校正的这些血压值,我们要分别对 SBP、 DBP 写 7 个赋值语句,总计 14 个。这样是不是太烦琐了?是的。这时候数组就可以派上用场了。
我们可以建两个数组 SBP、 DBP 分别用来存储 SBP1SBP7、 DBP1DBP7。就像下面这样有一排格子,每个格子有一个编号, SAS 依据格子的编号进行数据的存取,这就是一维数组,数据排列就在一个维度上:行。
数组 SBP :
1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|
SBP1 | SBP2 | SBP3 | SBP4 | SBP5 | SBP6 | SBP7 |
数组 DBP:
1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|
DBP1 | DBP2 | DBP3 | DBP4 | DBP5 | DBP6 | DBP7 |
当然,我们甚至可以直接建一个数组,同时把 7 次 SPB, DBP 的值打包在一起,这就是二维数组,数据排列在两个维度上:行和列。
1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|
SBP1 | SBP2 | SBP3 | SBP4 | SBP5 | SBP6 | SBP7 |
DBP1 | DBP2 | DBP3 | DBP4 | DBP5 | DBP6 | DBP7 |
上面只是给出了数组的概念示意图。实际操作时涉及两个核心问题:一是如何定义数组;二是如何访问数组。
- 定义数组 SAS DATA 步中,我们通过语句 ARRAY 来定义数组。其具体语法格式请参考语法 2-1:
ARRAY array-name{number-of-elements} <$> <(initial-value-list)>;
关于数组语法的一些解释如下所述。
- 元素个数可以用{*}代替,表示让SAS自动计数,也可以指定具体的数字,如{7},还可以指定一定的数字范围,如{1:7}。
- 元素名可以是变量名,也可以是SAS自定义的变量,如_ALL_(标示所有定义的变量,但是变量类型需要相同) , NUMERIC(所有数字变量)以及_CHARACTER_(所有字符变量),还可以是_TEMPORARY_(临时变量)。
- <>表示其中的内容并非必须有。例如, $只有在数组元素为字符型时才用到, length也是。数组元素及其初始值也并非必需,如果指定数组元素初始值的话,应该在小括号中指定。
程序2-14 定义数组
*===定义数组;
*===sbp1-sbp7是sbp1到sbp7的缩略写法;
array sbp{7} sbp1-sbp7;
array dbp{1:7} dbp1-dbp7;
*===带初始值;
array sbp{1:7} sbp1-sbp7 (163 164 167 171 155 158 154);
array dbp{7} dbp1-dbp7 (98 99 92 94 95 93 93);
*===定义二维数组;
array bp{2,1:7} sbp1-sbp7 dbp1-dbp7 ;
array bp{2,7} sbp1-sbp7 dbp1-dbp7 (163 164 167 171 155 158 154 98 99 92
94 95 93 93);
- 访问数组 访问数组的元素时,我们需要告诉 SAS 数组元素的地址,数组中元素的地址用数组名加角标的形式arrayname{i} 表示。配合前面已经介绍过的 DO 循环语句,我们可以遍历 数组中的所有元素(见图 2-13),进行各种数据操作,如果希望进行前面提到的加减校正,把 PUT 语句换成赋值语句即可.
程序2-15 访问数组元素
data tmp;
*===定义数组;
array sbp{7} sbp1-sbp7 (163 164 167 171 155 158 154);
array dbp{7} dbp1-dbp7 (98 99 92 94 95 93 93);
array bp{2,7} sbp1-sbp7 dbp1-dbp7 (163 164 167 171 155 158 154 98 99
92 94 95 93 93);
*===遍历一维数组;
do i=1 to 7;
put "第" i "次测量的SBP为: " sbp{i};
put "第" i "次测量的DBP为: " dbp{i};
end;
*===遍历二维数组;
do m=1 to 2;
do n=1 to 7;
put "血压类型为: " m ",血压测量次数为: " n ",血压测量值为: " bp{m,n};
end;
end;
run;
在 SAS 里,特别是 DATA 步中,如果希望更加方便、快捷地处理数据,我们就必须了解函数和 CALL 例程。 SAS 函数可以接收参数,执行一些运算和操作,然后返回一个值。CALL 例程与 SAS 函数类似,不过不能用于赋值的语句或表达式中。我们通过一个简单的例子感受下函数和 CALL 例程的应用。
程序2-16 函数与列程应用示例
data _null_;
length FullName_ByFunction FullName_ByRoutine $10;
FamilyName="Gu";
GivenName="Hongqiu";
*===用函数生成全名;
FullName_ByFunction=catx(" ",GivenName, FamilyName);
*===用列程生成全名;
call catx(" ",FullName_ByRoutine, GivenName, FamilyName );
*===Log中查看结果;
put "Fullname Generatedy by Function: " FullName_ByFunction;
put "Fullname Generatedy by Routine: " FullName_ByRoutine;
run;
笔者粗略统计了下, SAS 中有将近 30 多类,总计达 520 个函数。这是一个比较庞大的体系,也是一个非常有利的武器,我们将在第 6 章专门论述。
SQL 是结构化查询语言(Structured Query Languag)的简称,自 1970 年 IBM 开发以来,作为关系型数据库查询工具的标准化语言而广泛使用。 SAS 自 6.06 版本引入 SQL 后,一直在增强完善其功能及其与 SAS 软件的兼容性,目前 SAS 9 中的 SQL 已经非常强大。通过 SQL,我们可以进行简单查询、子查询,不用排序就可以进行表的连接、集合运算、创建视图和表、创建宏变量等一系列操作。本小节我们仅就 SQL 语言做一概要式介绍,具体的应用我们会结合后面的实例再讨论。
SQL 最简单的应用就是用 SELECT 语句做查询。 SELECT 语句包含了一系列有序的从句,具体可见语法 2-2。Help 中 <> 表示里面的东西选用。因此,必用的就只有 SELECT 和 FROM 了,比如下面的例子就用 SQL 查看 sashelp.class 中的姓名、性别以及年龄。
> , ...>>; Quit;Proc sql; SELECT <DISTINCT|UNIQUE> object-item-1<, object-item-2, ...> <INTO macro-variable-specification-1<, macro-variable-specification-2, ...>> FROM from-list
程序2-17 最简单的一个SQL过程
proc sql;
select name, sex, age
from sashelp.class;
quit;
当然其他从句也是非常实用的。比如,用 WHERE 可以进行条件筛选,用 GORUPBY 可以进行分组统计,用 HAVING 可以对分组统计的结果进行条件筛选,用 ORDERBY 可以对结果进行排序。初接触时,可能对这些从句的顺序记忆有些混淆,笔者个人就用 SFW、 GHO 来记它。 sfw 是一种位图格式文件的扩展名, gho 是 ghost 镜像文件的扩展名。
下面是一个完整的,利用了所有 SELECT 从句的例子。目的是先按性别分组统计人数、平均身高,然后挑出身高大于 62 的组,最后按人数多少排序。
程序2-18 PROC SQL SELECT语句全从句示例
proc sql;
select sex, count(name) as cnt_name ,mean(height) as m_height
from sashelp.class
where age>=12
group by sex
having m_height>62
order by cnt_name;
quit;
MACRO(宏)这个术语可能对我们来说并不陌生,宏就是实现自动化操作的一种工具。在 EXCEL 里我们就曾接触过,只是大部分人很少用而已。在 SAS 里,宏工具是一个用来自动化和定制化 SAS 代码的文本处理工具。
SAS 的强大,很大一部分原因就是宏工具的存在。宏的本质是文本替换,但是通过文本替换,可以实现 SAS 代码的自动化生成,动态生成以及 SAS 代码的条件结构,也就是说,不仅可以让 SAS 代码自己去写 SAS 代码,而且还可以根据不同的条件写不同的代码,这很符合“元编程”的理念。也正是因为这样,很多 SAS 开发者,疯狂开发自己的宏,从而避免很多重复性的代码编写工作,实现更多自动化、智能化的处理。
SAS宏语言分为两大块:宏变量和宏程序。宏变量是不必限定在 DATA步使用的变量,即独立于数据集的变量。宏变量分为系统宏变量和用户自定义宏变量。最常规的情况下,我们可以用 %LET 语句定义宏变量, %PUT 语句查看宏变量。正如前面所说,宏本质是文本替换,宏变量也是用简单的文本去替换更长更复杂的文本。例如,我们可用一小段文本“PUMC”替换更长的“Peking Union Medical College”。
程序2-19 宏变量
*===自定义;
%let PUMC=Peking Union Medical College;
*===查看系统自带;
%put &sysdate;
*===查看自定义;
%put &PUMC;
宏程序同宏变量类似,不过宏程序还有其他特性:①可以包含编程语句,包括 DATA步和 PROC 语句;②可以接受参数。比如,我们可以定义一个打印指定数据集、指定变量的宏。在定义宏程序时,用 %MACRO开头,用 %END结尾,使用宏时,用 %宏名称即可。
程序2-20 MACRO定义和调用
*===定义Macro;
*===通过data和var这两个参数指定数据集和变量;
%macro prtdsvar(data=, var=);
proc print data=&data;
var &var;
run;
%mend;
*===调用Macro;
%prtdsvar(data=sashelp.class, var=name sex)
关于宏,本节仅做概念性介绍,具体的内容我们将在第 10 章进行详细讨论。
SAS 的学习曲线比较陡峭,其原因之一就是很多 SAS 学习者没有深入理解 SAS 的运行机制,其中最为重要的机制就是 PDV(Program Data Vector)与 DATA 步自循环。
很多时候,即使是写了很多 SAS 程序、用了很长时间 SAS 的人,也总是会对 SAS DATA 步运行出的结果感到莫名其妙,对发生的错误更是一头雾水,但是如果能够静下心来,了解 PDV、理清 SAS 的运行机制,很多疑惑或许就迎刃而解了。
SAS 系统处理 SAS DATA步时,分两步:编译和执行。经典的 DATA步,基本按照图 2-14的流程来。
具体而言,在编译和执行阶段, SAS 会分别进行见表 2-6 的操作。
表 2-6 编译和执行阶段具体动作
1.编译 : 提交代码后, SAS 进行编译,此时 SAS 要确定每个变量的类型和长度,并确定变量是否有必要进行类型转换。具体如下:
- 检查代码的语法
- 将代码翻译成机器语言
- 如果是用 INPUT 语句读入原始数据,则建立输入缓冲区(Input Buffer),如果是读入 SAS数据集,则直接建立程序数据向量
- 建立程序数据向量(Program Data Vector, PDV),包含
- SAS 数据集变量以及 SAS 语句计算生成的变量
- 自动变量 N、 _ERROR
- 建立 SAS 数据集以及变量属性的描述信息
- 数据集名字、类型(数据文件、 SAS 视图)、创建日期时间
- 变量名称、类型(字符、数字)、序号等 |
- 执行:默认情况下,一条观测要经历一次 DATA 步的迭代
- 从 DATA 语句开始,将 N 值设定为 1(随着 DATA 语句的每次迭代,变量 N 自动加 1),ERROR=0(发生错误时 ERROR 会变成 1,程序终止)
- 把 PDV 中的变量设为缺失
- 用 INPUT 语句把一条数据记录从原始数据读入缓存区,或者用 SET, MERGE, MODIFY或 UPDATE 语句,把 SAS 数据集里的一条观测值读入到 PDV
- 对当前观测执行 DATA 步中后续的程序语句
- 执行完最后的语句, SAS 自动完成输出、返回、重设动作
- 输出,即把观测从 PDV 写入数据集,自动变量(N、 ERROR 不会输出)
- 返回:系统自动返回 DATA 步开头
- 重置:将 PDV 里由 INPUT 语句和赋值语句创建的变量设置为缺失,但由 SET,MERGE, MODIFY 或 UPDATE 语句读入的变量不置为缺失
- SAS 开始计下一次迭代,读入下一条记录或观测,对当前观测执行后续的编程语句
- 到达要读入的 SAS 数据集或者原始文件的数据末尾时, DATA 步终止
在上面的过程中,有两个概念不是很好理解:一是输入缓冲区(Input Buffer);二是 程序数据向量(Program Data Vector)。这两个概念都是内存里的一个逻辑区域,我们简要示图如图 2-15 所示。
Buffers 是系统内存的缓冲区,我们可以先不细究。如上图,分别展示了读入原始数据和读入 SAS 数据集时的流程。 (1)读入原始数据时:原始数据先读入 Input Buffer,再从 Input Buffer 转换到 PDV,最后从 PDV 输出到 SAS 数据集。 (2)读 SAS 数据时:把数据集观测直接读入到 PDV,再从 PDV 输出到数据集。 我们再次以一个小程序为例,看看 Input Buffer与 PDV,了解 SAS DATA步的运行机制。
程序2-21 PDV演示程序
data demoPDV;
input ID $ Chinese Math English;
Sum=Chinese+Math+English;
datalines;
S001 80 99 93
S002 90 85 95
S003 83 88 81
;
run;
在编译阶段, SAS 就知道这个要建立的数据集叫 DemoPDV, 有 ID、 Chinese、 Math、English 以及 Sum 五个变量,其中 ID 为字符型。 SAS 给他们建立好 Input Buffer 和 PDV Input Buffer:内存里开辟空间,以便中转数据。
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
PDV:从 Input 语句或者 SET、 MERGE、 UPDATE 语句获取变量信息,建立好数据变量
ID | Chinese | Match | Eng | Sum | Error | N |
---|---|---|---|---|---|---|
运行阶段: (1)设置 INPUT 中的变量为缺失 ( 字符变量为空白,数字变量为小数点 ),并设置 自动变量 N=1, ERROR=0;
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
ID | Chinese | Match | Eng | Sum | Error | N |
---|---|---|---|---|---|---|
. | . | . | . | 0 | 1 |
(2) INPUT 语句读入第一条记录, Input Buffer 和 PDV 的状态。方框可以理解为在运行的程序部分; 开始 INPUT 语句:
data demoPDV;
input ID $ Chinese Math English;
Sum=Chinese+Math+English;
datalines;
S001 80 99 93
S002 90 85 95
S003 83 88 81
;
run;
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
S | 0 | 0 | 1 | 8 | 0 | 9 | 9 | 9 | 3 |
ID | Chinese | Match | Eng | Sum | Error | N |
---|---|---|---|---|---|---|
. | . | . | . | 0 | 1 |
读入第一个变量 ID:
data demoPDV;
input ID $ Chinese Math English;
Sum=Chinese+Math+English;
datalines;
S001 80 99 93
S002 90 85 95
S003 83 88 81
;
run;
ID | Chinese | Match | Eng | Sum | Error | N |
---|---|---|---|---|---|---|
S001 | . | . | . | . | 0 | 1 |
读入第二个变量:
data demoPDV;
input ID $ Chinese Math English;
Sum=Chinese+Math+English;
datalines;
S001 80 99 93
S002 90 85 95
S003 83 88 81
;
run;
ID | Chinese | Match | Eng | Sum | Error | N |
---|---|---|---|---|---|---|
S001 | 80 | . | . | . | 0 | 1 |
如此循环,直到最后一个变量 sum:
data demoPDV;
input ID $ Chinese Math English;
Sum=Chinese+Math+English;
datalines;
S001 80 99 93
S002 90 85 95
S003 83 88 81
;
run;
ID | Chinese | Match | Eng | Sum | Error | N |
---|---|---|---|---|---|---|
S001 | 80 | 99 | 93 | 272 | 0 | 1 |
(3)完成所有 DATA 步后续语句, SAS 自动完成输出数据集。
data demoPDV;
input ID $ Chinese Math English;
Sum=Chinese+Math+English;
datalines;
S001 80 99 93
S002 90 85 95
S003 83 88 81
;
run;
ID | Chinese | Match | Eng | Sum | Error | N |
---|---|---|---|---|---|---|
S001 | 80 | 99 | 93 | 272 | 0 | 1 |
将上面 PDV 里除了自动变量 ERROR, N 外,其他变量自动输出到数据集DemoDPV。
(4)返回 DATA 步第一语句,初始化 PDV。
data demoPDV;
input ID $ Chinese Math English;
Sum=Chinese+Math+English;
datalines;
S001 80 99 93
S002 90 85 95
S003 83 88 81
;
run;
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
S | 0 | 0 | 2 | 9 | 0 | 8 | 5 | 9 | 5 |
ID | Chinese | Match | Eng | Sum | Error | N |
---|---|---|---|---|---|---|
. | . | . | . | 0 | 2 |
(5)开始读入第二条记录的第一个变量 ID:
ID | Chinese | Match | Eng | Sum | Error | N |
---|---|---|---|---|---|---|
s002 | . | . | . | . | 0 | 2 |
(6)如此循环重复,读完最后一条记录的最后一个变量,写入数据集。
ID | Chinese | Match | Eng | Sum | Error | N |
---|---|---|---|---|---|---|
S003 | 83 | 88 | 81 | 252 | 0 | 2 |
(7)再次返回第一条 DATA 语句,发现已经没有数据可以读取,直到这时,DATA 步才彻底结束。
ID | Chinese | Match | Eng | Sum | Error | N |
---|---|---|---|---|---|---|
s002 | . | . | . | . | 0 | 4 |
如何粗略略的验证上述步骤呢?我们可以尝试运行程序 2-22 验证 PDV,看 LOG 窗口给我们的信息提示。
程序2-22 验证PDV
data demoPDV;
put "第" _n_ "次运行前: " _all_;
input ID $ Chinese Math English;
Sum=Chinese+Math+English;
put "第" _n_ "次运行后: " _all_;
datalines;
S001 80 99 93
S002 90 85 95
S003 83 88 81
;
run;
LOG 的结果显示:
第1 次运行前: ID= Chinese=. Math=. English=. Sum=. _ERROR_=0 _N_=1
第1 次运行后: ID=S001 Chinese=80 Math=99 English=93 Sum=272 _ERROR_=0 _N_=1
第2 次运行前: ID= Chinese=. Math=. English=. Sum=. _ERROR_=0 _N_=2
第2 次运行后: ID=S002 Chinese=90 Math=85 English=95 Sum=270 _ERROR_=0 _N_=2
第3 次运行前: ID= Chinese=. Math=. English=. Sum=. _ERROR_=0 _N_=3
第3 次运行后: ID=S003 Chinese=83 Math=88 English=81 Sum=252 _ERROR_=0 _N_=3
第4 次运行前: ID= Chinese=. Math=. English=. Sum=. _ERROR_=0 _N_=4
最后补充说明一下:上面所展示的都是 SAS 默认的、最基础、最简单的运行机制。当 DATA 步有循环、选择语句,有 OUTPUT、 RETAIN 等语句时, SAS 的处理流程会有 所不同。
初学 SAS 者,或多或少都会对 @ 与 @@ 的理解有些吃力。官方对 @ 的说法是:INPUT语句尾部的 @是行保持符,主要作用是保持数据行停留在此行,不要跳到下一行。@ 称为单尾 @, @@ 称为双尾 @,很多情况下,我们连一个 @ 也不用,我姑且称之为无尾。那么什么情况下用无尾、什么情况下用单尾、什么情况下用双尾呢?以下是笔者总结的一些原则:
- 当DATALINES数据行里要读入的数据列数=要读入的变量数,也就是说一行就是一条观测时,无尾。
- 当DATALINES数据行里要读入的数据列数>要读入的变量数,而且是整数倍时,也就是说一行= K*条观测(K为≥1 的整数),用@@。
- 当一个DATA步里有多个INPUT语句时,我们需要单尾@。
程序2-23 @与@@示例程序
*=== 数据列数=变量数;
data test1;
input id x y z;
datalines;
1 98 99 97
2 93 91 92
;
run;
*=== 数据列数=变量数,多个input 语句;
data test2;
input id@;
input x@;
input y@;
input z@;
datalines;
1 98 99 97
2 93 91 92
;
run;
*=== 数据列数=k*变量数;
data test3;
input id x y z @@;
datalines;
1 98 99 97 2 93 91 92
;
run;
关于 @、 @@ 与跳行,笔者曾简单总结了如下原则:
- 无尾Hold不住立即跳。
- 一尾(@) Hold当前INPUTY语句不跳,但若刚好是DATA步最后一个INPUT语句,跳
- 二尾(@@)打死都不跳。
- 最后,无论多少尾,数据行末尾必定自动跳。
例如,实例程序 2-24 @ 与 @@ 的辨析中第一个程序,由于 INPUT X 后面有 @,且不是最后一个 INPUT 语句,故读完 X=1 后,不跳行,继续读 Y=2, 由于 INPUT Y 后无尾,立即跳行,故读 Z 时为 Z=4,又因 INPUT Z 后有 @@,虽然这是最后一个 DATA 步的 PUT 语句,不跳,程序返回开头,开始读第二条观测, X=5,不跳, Y=6,跳, Z=7。故最终的结果为两条观测, X, Y, Z 的值分别为: 1, 2, 4; 5, 6, 7。第二个程序,答案是 1,4, 5,各位读者能运用上面的原则得出答案吗?
程序2-24 @与@@的辨析
data test;
input x @; /*单个@,能Hold住,读后不跳*/
input y; /*没有@, Hold不住,读后跳*/
input z @@; /*两个@, Hold住没问题,但数据行末尾,读后自动跳*/
datalines;
1 2 3
4 5 6
7 ;
run;
data test;
input x ; /*无@, Hold不住,读后立即跳*/
input y @@; /*两个@, Hold住,读后不跳*/
input z @; /*单个@,但是是最后一个INPUT语句,跳*/
datalines;
1 2 3
4 5 6
7 ;
run;
很多 SAS 初学者抱怨 SAS 的帮助文档太复杂,难以读懂。其实,真正说起来, SASHelp 文档才是这世界上学习 SAS 最好的教材,对比 R 软件包的 Help 文档, SAS 的文档可以让我们感动到流泪。
SAS 的帮助文档(SAS Help),窃以为,是市面上所有统计软件里做得最有诚意的作品。 SAS Help 是 SAS 公司投入大量的精力打造的体系最为完整,措辞最为规范,获得最为方便,知识最为权威的 SAS 教材。打开 SAS Help 的官方网站(http://support.sas.com/documentation/),如图 2-16 所示,我们可以感受下那份满满的诚意。
我们以最新的 9.4 版本为例,官网上的 Help 文档都可以轻易获得, HTML 或者 PDF任挑(见图2-17),而且 PDF 文档的品质完全可以媲美精美的书籍(见图 2-18),更重要的是,我们都可以免费下载。
如果嫌弃 HTML 打开太麻烦, PDF 也懒得去下载,那也没有关系。只要我们在本地安装了 SAS,我们就可以随时在本地查看我们所购买的模块的帮助文档(见图 2-19)。不过需要留意的是,只有购买、安装了某一模块, Help 里才可以查到其相应的文档。
SAS Help 这么好的资源,通常被很多 SAS 教材给忽略了,他们绝口不提这档子事。当然也有一些 SAS 教材强调了多看帮助文档(SAS Help)在学 SAS 时的重要性,但往往另一问题又被忽略了:如何迈出第一步?怎样看懂 SAS Help ?
SAS Help 里会涉及一些元素和符号,还有一些特定的风格。了解其风格传统后我们再去阅读 SAS Help,就会轻松很多。比如我们看看我们以后将要用到的 ODS 画图过程 PROC SGPLOT 的 Help,就会发现有大小写、有粗细、有斜体,还有 <>, |,…等符号。所有这些,如何理解? SAS Help 的惯例体系,由四部分组成。 (1)语法成分:包括关键词和参数。关键词通常是语句的第一个单词或者头两个单词(如 PROC 语句、 CALL 列程语句),如 SAS 的过程名,语句名。参数是紧跟关键词后面或者等号后面的常量、变量或者表达式。 (2)风格惯例: SAS Help 里有三种风格,大写粗体、大写以及小写斜体。具体可见图 2-20。其中大写粗体用来标示过程、语句以及函数名称;大写用来标示参数为字符常量;小写斜体用来标示需要用户提供的参数或值。编程时,大写粗体、大写的文字必需原样照抄。 (3)特殊字符:特殊字符包括 <>、 =、 |、…、 “ ”、;等。
- <>,可选用的参数或者选项。
- =,赋值语句。
- |,互斥参数或参数值,只能选择其一,不能同时选择。
- …,重复。
- “”,值必需放在引号中。
- ;,语句结束。 (4) SAS 库及外部文件的引用, libref 以及 fi leref 标示库和文件关联名,用 SASlibrary 和 f le-specif cation 标示外部文件。
如果希望通读 SAS Help,最合适的方式应该是下载 PDF 文件来阅读和做标记。不过更多的使用场景是,对于某个过程或是某个语句,我们记不太清它的语法规则了,这时,我们希望快速查找到其语法参考手册。在 EG和 Studio中,有自动语法补全的提示,在 DMS下,我们可以通过 Help 菜单下的 SAS Help and Documentation 启动 SAS Help 然后进行检索。
比如说,我们在导入数据时,对 PROC IMPORT 的语法比较模糊,在检索框里输PROC IMPORT,就可以看到如图 2-21 所示的界面。
有时候,很多模块里都包含了检索的内容,需要我们留意下每条检索结果蓝色大写字的下面一行字,这行字给出的是这个链接是属于哪个模块里的语法参考说明。一般对于PROC 步,我个人喜好选择带「Overview:」的那个链接,里面从语法、概览到示例都有,且比较详细(见图 2-22)。此外,在检索时,选择合适的检索词有一定的讲究:检索过程步可以用“PROC ×××”“××× Procedure”;检索语句时可以用“××× Statement”,当然也可以直接用“×××”检索(××× 表示关键词)。
为了配合 SAS Help 文档, SASHelp 逻辑库下自带了很多数据集,熟悉这些数据集,有助于我们利用他们做一些简单测试。例如,本书就用了其中的三个常用的数据集,关于其他更多数据库的信息,可以参考帮助文档 SAS Help Datasets。 (1) class 数据集,学生数据集,里面包括了姓名、性别、年龄、身高、体重等信息。 (2) cars 数据集, 2004 年各大品牌汽车基本情况及其售价,包括了品牌、型号、车型、产地、驱动类型、售价、车身情况、速度等信息。 (3) heart 数据集,弗莱明翰心脏研究队列数据,包括生存状态、性别、年龄、身高、体重、吸烟、饮酒以及血压血脂等信息。
本章对 SAS 编程里最常用的基本概念,如逻辑库、数据集、 SAS 编程语言、 SAS 运行机制等做了较为详细的介绍。当然,这些介绍仍然比较简略,目的是让大家对 SAS 编程有个框架式的了解,而不是为了替代 SAS 本身的 Help 文档。因此,最后我们详细阐述了 SAS Help 的语法风格,这将有助于我们真正将 SAS Help 查阅起来、用起来。