SAS语言与数据管理

SAS系统强大的数据管理能力、计算能力、分析能力依赖于作为其基础的SAS语言。SAS语 言是一个专用的数据管理与分析语言,它的数据管理功能类似于数据库语言(如FoxPro), 但又添加了一般高级程序设计语言的许多成分(如分支、循环、数组),以及专用于数据管 理、统计计算的函数。SAS系统的数据管理、报表、图形、统计分析等功能都可以用SAS语言 程序来调用,只要指定要完成的任务就可以由SAS系统按照预先设计好的程序去进行,所以SAS 语言和FoxPro等一样是一种第四代语言。

本章简单介绍SAS语言的基本成分与规则,SAS语言如何用来管理数据,SAS语言作为一个 统计计算语言的用法,以及SAS过程使用的初步知识。

SAS语言构成

SAS语句

SAS语言程序由数据步和过程步组成。数据步用来生成数据集、计算、整理数据,过 程步用来对数据进行分析、报告。SAS语言的基本单位是语句,每个SAS语句一般由一个关键 字(如DATA,PROC,INPUT,CARDS,BY)开头,包含SAS名字、特殊字符、运算符等,以分号 结束。

SAS关键字是用于SAS语句开头的特殊单词,SAS语句除了赋值、累加、注释、空语句以外 都以关键字开头。SAS名字在SAS程序中标识各种SAS成分,如变量、数据集、数据库,等等。SAS 名字由1到8个字母、数字、下划线组成,第一个字符必须是字母或下划线。SAS关键字和SAS 名字都不分大小写。

SAS表达式

SAS数据步程序中的计算用表达式完成。表达式把常量、变量、函数调用用运算符、 括号连接起来得到一个计算结果。

SAS常量主要有数值型、字符型两种,并且还提供了用于表达日期、时间的数据类型。例 如

数值型常数可以用整数、定点实数、科学计数法实数表示。字符型常数为两边用单撇号或 两边用双撇号包围的若干字符。日期型常数是在表示日期的字符串后加一个字母d(大小写均 可),中间没有空格。时间型常数是在表示时间的字符串后加一个字母t。日期时间型常数在 表示日期时间的字符串后加字母dt。

因为SAS是一种数据处理语言,而实际数据中经常会遇到缺失值,比如没有观测到数 值,被访问人不肯回答,等等。SAS中用一个单独的小数点来表示缺失值常量。

SAS变量的基本类型有两种:数值型和字符型。日期、时间等变量存为数值型。SAS的 数值型变量可以存储任意整数、定点实数、浮点实数,一般不关心其区别。数值型变量在数 据集中的存贮一般使用8个字节。SAS的字符型变量缺省的长度是8个字符,但是如果在INPUT 语句中输入字符型变量时指定了长度则不受此限制。可以用LENGTH语句直接指定变量长度,LENGTH 语句一般应出现在变量定义之前,格式为:

LENGTH 变量名 $ 长度;

例如

LENGTH name $ 20;

SAS运算符包括算术、比较、逻辑等运算符。

算术运算符为 + - * / **,运算优先级按通常的优先规则。

比较运算符用于比较常量、变量的值大小、相等,包括

		=		^=		>		<		>=		<=		IN
		EQ		NE		GT		LT		GE		LE

其中EQ等名字和=等特殊字符是同一运算符的等价写法。比较运算符得到“真”或“假” 的结果,主要用于需要条件的分支、循环等语句中。运算符IN是一个SAS特有的比较运算符, 用来检查某个变量的取值是否在一个给定列表中,比如

prov in ('Beijing', 'Tianjin', 'Shanghai', 'Chongqing')

可以判断变量prov的取值是否为四个直辖市之一。

逻辑运算符用来连接比较得到的结果以构成复杂的条件,有三种逻辑运算符:

&(AND) |(OR) ^(NOT)

其中AND是&(与)的等价写法,OR是|(或)的等价写法,NOT是^(非)的等价写法 。例如

(salary >= 1000) AND (salary < 2000) 表示工资收入在1000-2000之间 (不含2000)

(age <= 3) OR (sex = '女') 表示三岁以下(含三岁)的婴儿及妇 女

NOT ((salary >= 1000) AND (salary < 2000)) 表示工资收入 在1000-2000之间

复杂的逻辑表达式最好用括号表示其运算优先级以免误记优先规则并可利于阅读程序。

其它的运算符还有用于连接两个字符串的||(两个连续的|号),用于取两个运算值 中较大一个的<>(比如3<>5结果为5),用于取两个运算值中较小一个的>< (比如3><5结果为3)。注意<>符在有些语言中用作“不等于”比较算符,而SAS 中用法则较特殊。

SAS程序规则

SAS程序由语句构成。每个语句以分号结尾(最常见的SAS编程错误就是丢失分号)。 因为分号作为语句结束标志,所以SAS语句不需要单独占一行,一个语句可以写到多行(不需 任何续行标志),也可以在一行连续写几个语句。SAS语言中只要允许用一个空格的地方就可 以加入任意多个空白(空格、制表符、回车),允许用空格的地方是名字周围、运算符周围 。比如,程序

proc print
        data=c9501;
    by              avg;

run;

proc print data=c9501;by avg;run;

是等效的。另外,SAS关键字和名字大小写不分,但字符型数据值要区分大小写,比如"Beijing" 和"BEIJING"被认为是不同的数据值。

在SAS程序中可以加入注释,注释使用C语言语法,用/*和*/在两端界定注释,这种注 释可以出现在任何允许加入空格的位置,可以占多行。我们一般只把注释单独占一行或若干 行,不把注释与程序代码放在同一行。注释的另一个作用是把某些代码暂时屏蔽使其不能运 行。下面是一个注释的例子:

/* 生成95级1班考试成绩的数据集 */
data c9501;
……

SAS程序包括数据步和过程步两种结构,每一个步是一段相对完整的可以单独运行的 程序。数据步用来生成、整理数据和自编程计算,过程步调用SAS已编好的处理过程对数据进 行处理。自己用SAS编程序进行计算主要在数据步中进行。

SAS数据步以DATA语句开头,以RUN语句结尾。DATA步中可以使用INPUT、CARDS、INFILE 、SET、MERGE等语句指定数据来源输入数据,也可以用赋值、分支、循环等编程结构直接生 成数据或对输入的数据进行修改。

SAS用作一般高级语言

SAS是一种专用的数据处理、统计计算语言,但是它也包含一般的高级语言编程能力 并扩充了许多数学、统计等方面的函数。我们先介绍SAS语言用来进行一般编程计算的功能, 然后再讲解其独特的数据处理功能。SAS数据步的数据输入、整理功能很强,希望进行复杂的 数据管理的读者可以根据本节和下节的内容用SAS实现强大的数据管理功能。希望实现自己的 统计计算算法的读者可以用SAS数据步编程,但是我们建议使用Splus来编算法程序,因为用Splus 编程更方便。

SAS语言的编程计算能力主要由SAS数据步提供(另外SAS还提供了一个SAS/IML模块可以进 行向量、矩阵运算,读者有兴趣可以自己学习)。所以,下面给出的例子如果没有写DATA语 句实际应该在例子前面加上DATA语句,在后面加上RUN语句才能运行。DATA语句以关键字DATA 开头,后面给出一个数据集名,这是本数据步要生成的数据集的名字,例如:

data tmp1;

也可以省略数据集名,这时SAS自动生成一个临时数据集名。还可以使用特殊名字_NULL_ ,表示本数据步不生成数据集。

赋值语句

在SAS中用赋值语句计算一个值并存放到变量中。格式为
		变量名 = 表达式;
例如:
avg = (math + chinese/120*100)/2;
isfem = (sex='女');
y=sin(x)**2;
newv = .;

其中第一个赋值语句用一个公式计算平均分数。第二个生成一个取值为0或1的变量,性别 为女时为1,否则为0。第三个使用了正弦函数和乘方运算。第四个给变量赋了缺失值。

注意想试验上述语句要把它们放入数据步中,并且等号右边的表达式中的各变量应该是存 在的,否则会得到缺失值结果。

输出语句

SAS数据步的输出一般是数据集,用赋值语句计算的结果会自动写入数据集。SAS也提 供了一个PUT语句,可以象其它语言程序的PRINT、WRITE(*,*)、printf等语句一样立即打印 输出结果。PUT语句在关键字后面列出要输出的各项,每一项可以是变量名或字符串,不能为 数值常量或表达式,各项之间用空格分开。PUT语句的输出结果显示在LOG窗口。例如:

data;
  x=0.5;
  y=sin(x);
  put  'Sine function value of '  x  'is '  y;
run;
结果将在运行记录窗口显示一行
Sine function value of 0.5 is 0.4794255386

另外,在PUT语句中使用“变量名=”来指定输出项可以显示带有变量名的输出结果,比 如把上程序中的PUT语句改为

  put  x=  y=;
则结果在LOG窗口显示为
X=0.5 Y=0.4794255386

PUT语句的输出项还可以指定具体列位置,比如,下面的PUT语句指定把X数值显示在 第10-20列,把Y数值显示在第30-40列,并保留6位小数:

put x 10-20.6 y 30-40.6;

在指定的列位置内,数值型数据靠右对齐,字符型数据靠左对齐。要保留的小数位数写在 一个小数点后面,如果变量为整数值或者字符型则不必指定小数位数。

PUT语句还可以使用类似C、FORTRAN语言的“域宽.精度”方式指定输出的宽度和精度 ,例如,

put x 20.8 y 20.8;

使X占用第1-20列,8位小数,右对齐;Y占用第21-40列,8位小数,右对齐。其中20.8 是多种SAS输出格式中最常见的一种,用于输出数值型数据,小数点前面为输出宽度,小数点 后面为输出精度(小数位数)。对于字符型变量,要指定其输出宽度可以用“$宽度.”的格 式,如“$10.”指定字符型变量输出宽度为10位。输出占不满指定宽度时,数值型数据向右 对齐,字符型数据向左对齐。

字符型常量可以用于PUT语句中,但不能规定占用的列位置或者宽度。

如果希望PUT语句的输出不产生换行,使下一个PUT的结果可以显示在同一行,只要在PUT 语句结尾处加一个@符,如:

put i @;

PUT语句的输出结果缺省情况下被送到运行记录窗口。在PUT语句之前用FILE语句可以 改变PUT语句的输出目的地。比如,在PUT语句之前用

file print;

可以把PUT语句的输出转向到输出窗口。在FILE语句中指定一个包含文件名的字符串可以 把PUT语句的输出转向到此文件中。比如

file 'tmp.out';

把后续的PUT语句输出转向到当前工作目录下的文件“tmp.out”中,生成输出文件tmp.out 。注意当前工作目录在SAS状态栏的右方显示,双击可以更改。文件名也可以指定全路径,比 如“C:\SAS\TMP.OUT”。

分支结构

如果需要在某条件满足时执行某一操作,可以用

		IF  条件  THEN  语句;

的结构。比如,如果X为正数则显示“X为正数”,可以用

IF  x>0  THEN  PUT  'X为正数';

有时我们在条件成立时需要进行的操作无法用一个语句完成,这时可以使用SAS提供 的 复合语句功能:只要把若干个语句用“DO;”语句和“END;”语句包围起来,就可以 把它们看作是一个语句,就可以用在需要指定一个语句的地方。比如,当X为正数时不仅显示X 为正数,而且将其加倍并显示值,可以用如下带有复合语句的IF结构:

IF  x>0  THEN  DO;
    PUT  ' X为正数';
    x = 2*x;
    PUT  x=;
    END;

以上的IF结构的用法只规定了条件成立时的操作,如果同时需要规定条件不成立时进 行什么操作,使用带有ELSE字句的IF结构:

		IF  条件  THEN  语句;
		ELSE  语句

其中“语句”均可以是复合语句。 例如,当X为非负时将X加倍,为负时将X取绝对值,用如 下程序:

IF  x>=0  THEN  x=2*x;
ELSE  x = -x;

注意SAS的分支结构的写法与其它语言有些不同,它不用ENDIF结束。

SAS的IF结构允许嵌套,但SAS不提供IF-ELSEIF-ELSE的多分支结构。SAS的SELECT 结构提供了更为灵活的多分支结构,可以实现比其它语言的IF-ELSEIF-ELSE结构更强的功 能。SELECT结构有两种基本用法,第一种为:

		SELECT (选择表达式);
			WHEN(值列表)  语句;
			WHEN(值列表)  语句;
			……
			OTHERWISE  语句;
		END;

其中“选择表达式”是一个取数值、字符型值的变量或表达式, “值列表”为一项或者若干项,多项之间逗号分开,每项可以是一个与选择表达式相同 取值类型的表达式。 “语句”可以是单个语句或复合语句。执行SELECT结构时,先计算出选择表达式和值列 表中的所有值,然后把选择表达式值由前向后与值列表中的值相比,发现相等值则执行对应 的语句,然后退出SELECT结构(不再查看后面的值列表)。如果选择表达式的值不等于任何 值列表中的值则执行OTHERWISE对应的语句,这种情况下没有OTHERWISE语句会出错。例如:

SELECT(month);
  WHEN('Feb', 'Mar', 'Apr')  put  '春天';
  WHEN('May', 'Jun', 'Jul')  put  '夏天';
  OTHERWISE  put  '秋天或冬天';
END;
根据MONTH值不同显示不同的季节。

SELECT语句的另一种形式为:

		SELECT;
			WHEN(条件)  语句;
			WHEN(条件)  语句;
			……
			OTHERWISE  语句;
		END;

这种SELECT语句没有选择表达式,而是在每一个WHEN语句指定一个条件(逻辑表达式), 执行第一个满足条件的WHEN后的语句。如果所有条件都不满足则执行OTHERWISE后的语句。例 如:

SELECT;
	WHEN(age<=12)  put  '少年';
	WHEN(age<35)  put  '青年';
	OTHERWISE  put  '中老年';
END;

注意上例中第二个WHEN语句的条件等价于age>12 and age<35,因为如果年龄小 于等于12的话则会执行第一个WHEN语句,然后退出SELECT结构,根本不会判断第二个条件。 这与其它语言中的IF-ELSEIF-ELSE结构的用法是一致的。

循环结构

SAS数据步可以使用丰富的循环结构,主要的是两种:计数DO循环和当型、直到型循环。

计数DO循环的写法是:

		DO  计数变量起始值  TO  结束值  BY  步长;
			循环体语句……
		END;

在DO和END之间可以有多个语句。程序先把计数变量赋值为起始值,如果此值小于等于结 束值则执行循环体语句,然后把计数变量加上步长,再判断它是否小于等于结束值,如果是 则继续执行循环体,直到计数变量的值大于结束值为止。上述结构中“BY 步长”可以省略, 这时步长为1。如果步长取负值,则继续循环的条件是计数变量大于等于结束值。例如:

data;
  DO  i = 1  TO  20  BY  2;
    j = i**3;
    put  i  3.  j  5.;
  END;
run;

可以输出一个1,3,5,7,…,19的立方表。

在循环体中可以用LEAVE语句跳出循环,相当于C语言的break语句。例如在上例中的 循环体最后加上这样一句可以在立方大于1000时停止循环:

if j>1000 then LEAVE;

在循环体内用CONTINUE语句可以立即结束本轮循环并转入下一轮循环的判断与执行。 比如:

data;
  do x=0 to 3.1415926  by  0.01;
    y = sin(x);
    if y<0 then CONTINUE;
    z = cos(x);
    put  x  5.2  y  20.7  z  20.7;
  end;
run;

这个程序对0到 之间的数每隔0.01计算正弦值,如果正弦值 为负则考虑下一个值,正弦值非负时计算余弦值并显示。因为正弦为负时没有任何结果显示 ,所以我们只能看到 /2为止的结果。

当型循环的语法是:

		DO  WHILE(循环继续条件);
			循环体语句……
		END;

程序先判断循环继续条件是否成立,成立时执行循环体语句,再判断循环继续条件,如此 重复,直到循环继续条件不再成立。例如,下面的程序判断1333333是不是素数:

data;
  x=1333333;
  i=3;
  DO  WHILE  (mod(x,i) ^= 0);
    i=i+2;
  END;
  if i<x then put x '不是素数';
  else  put  x  '是素数';
run;
其中mod(x,i)表示x除以i的余数。

直到型循环的写法是:

		DO UNTIL (循环退出条件);
			循环体语句……
		END;

程序先执行循环体,然后判断循环退出条件是否成立,成立则结束循环,否则继续。注意 每轮循环都是先执行循环体再判断是否退出。例如:

data;
  n=0;
  do until (n>=5);
     n+1;
     put n=;
  end;
run;

可以依次输出n=1,2,3,4,5,当n=5时退出条件“n>=5”满足,循环结束。上例中 语句n+1是一种特殊的写法,叫做累加语句,等价于n=n+1。

事实上,SAS的循环语句比上面所述还要灵活得多,它在DO语句中可以指定一个循环 列表,比如:

data;
  do i=3,7, 11 to 17 by 3 while (i**2<200);
     j=i**2;
     put i j;
  end;
run;

循环变量i取5,7,11,14循环体被执行,当i取17时i的平方为289故循环体不被执行,循 环结束。注意WHILE条件只作用于用逗号隔开的最后一项。

数组

SAS可以把一组同为数值型或同为字符型的变量合在一起,使用同一个名字称呼,用 下标来区分。这与通常的程序设计语言中的数组略有区别,通常的程序设计语言中数组元素 没有对应的变量名,而SAS数组每个元素都有自己的变量名。

一、数值型数组

定义数值型数组的格式为:

		ARRAY  数组名维数说明数组元素名列表初始值表);
例如:
ARRAY  tests(3)  math chinese english  (0, 0, 0);

数组名是一个合法的SAS名字且不能与同一数据步中的变量重名。对一维数组,维数说明 只要说明元素个数,这时下标从1开始。数组元素名列表列出这个数组的各个元素实际代表的 变量名,各变量名以空格分隔。比如,上例中tests(1)代表数学成绩,tests(2)代表语文成 绩,tests(3)代表英语成绩。初始值表给各数组元素赋初值,按顺序对应。

数组说明中初始值表可以省略,这时其初始值为相应数组元素的值(如果其数组元素 还没有值则初值为缺失值)。

数组说明中的数组元素名列表可以省略,这时其元素也有对应的变量名,变量名为数 组名后附加序号,比如:

ARRAY  x(3);

中数组x的各元素名为x1,x2,x3。

也可以在说明维数时用“下标下界:下标上界”来说明一个其它的下标下界,如

ARRAY  sales(95:97)  yr95-yr97 ;

这时sales(95)为yr95,sales(96)为yr96,sales(97)为yr97。上面的变量名列表是一种 特殊的语法,在用到变量名列表时如果连续写几个前面字母相同,后面是连续的序号的变量 ,只要写出第一个和最后一个,中间用减号连接。

一维数组的维数说明还可以是一个星号,这时数组大小由提供的元素列表中的变量个 数决定,如上面的数组tests可以等价地说明为:

ARRAY  tests(*)  math chinese english  (0, 0, 0);
可以用函数DIM(数组名)来获得数组的长度。

可以定义二维数值型数组,只要在维数说明中指定用逗号分开的两个下标界说明,例 如:

array  table(2,2)  x11 x12 x21 x22;

说明table(1,1)为x11,table(1,2)为x12,table(2,1)为x21,table(2,2)为x22。二维数 组元素按行排列。

二、字符型数组

定义字符型数组的语法略复杂,它需要加一个$符来说明数组元素类型为字符型 ,并且要说明每一元素所能存储的字符串的最大长度。说明格式如下:

	ARRAY  数组名维数说明) $ 元素长度说明 数组元素名列表初始值表);
例如:
ARRAY  names(3)  $ 10  child  father  mother;
字符型数组其它方面用法与数值型相同。

三、临时数组

上面格式说明的数组都是把若干个变量集合在一起使用同一个数组名称呼,每个 数组元素是一个独立的变量。SAS也提供了与其它程序设计语言相同的数组,即数组元素只由 数组名和序号决定,没有对应的变量名。这种数组叫做临时数组,定义格式为:

	ARRAY  数组名维数说明) _TEMPORARY_ (初始值表);
可见临时数组就是在数组说明中用_TEMPORARY_代替了数组元素列表。例如:
ARRAY  x(3)  _TEMPORARY_  (0, 0, 0);

说明了一个有三个元素的临时数组x。其元素为x(1),x(2),x(3),即使变量x1,x2,x3 存在也与此数组无关。临时数组的特点是它只用于中间计算,最终不被写入数据集。并且临 时数组与其它变量不同的是,它在数据步隐含循环(后面会解释此概念)中能自动保留上一 步得到的值。临时数组当然也可以有多维数组,或字符型数组。

四、使用数组

临时数组的使用与其它程序设计语言中的数组作用相同,可以存放性质类似的数 据进行处理。SAS以变量为元素的数组可以方便变量的循环处理,比如,读入了comp1-comp10 十个计算机销售额变量,prin1-prin6六个打印机销售额变量,希望计算其总和,可以用如下 的数组说明与DO循环配合进行:

data sales;
  input  comp1-comp10  prin1-prin6;
  ARRAY  y(*) comp1-comp10  prin1-prin6;
  tot=0;
  do i=1 to DIM(y);
    tot + y(i);
  end;
  cards;
………
;
run;

此例中数组说明用了星号说明维数,求总和时用了累加语句。事实上,在数组说明的数组 元素列表部分除了列出具体的变量名表外,还可以用特殊名字_NUMERIC_代表所有数值型变量 的列表,用_CHARACTER_代表所有字符型变量的列表,用_ALL_代表所有变量的列表(用_ALL_ 时所有变量应该同为数值型或同为字符型,否则出错)。所以上例中的数组y的说明中还可以 用_NUMERIC_或_ALL_代替变量名列表。

实际上,SAS为变量累加提供了一个专门的函数SUM(OF …),比如上面的tot变量可以 用SUM(OF comp1-comp12 prin1-prin6)计算。这个例子主要为说明如何循环处理多个变量。

函数

SAS提供了比一般程序设计语言多几倍的标准函数可以直接用在数据步的计算中,其 中包括所有语言都有的数学函数、字符串函数,还包括特有的统计分布函数、分位数函数、 随机数函数、日期时间函数、财政金融函数,等等。

这些函数的调用方法类似其它语言,比如求x1,x2,x3三个自变量的和可以用函数SUM(x1,x2,x3) 。另外,SAS还提供了函数调用的另一种语法以便于把多个数据集变量作为函数自变量,其格 式为“函数名(OF 变量名列表)”,其中变量名列表可以是任何合法的变量名列表,比如x1 ,x2,x3的和等价地可以用SUM(OF x1 x2 x3)或SUM(OF x1-x3)表示。注意两种写法不能混 在一起,比如SUM(OF x1,x2,x3)和SUM(x1-x3)都是错的。

本小节对重要的函数加以介绍,其它详见《SAS软件:Base SAS软件使用手册》(高惠璇 等编译,中国统计出版社出版)。

 

一、数学函数

此外还有符号函数SIGN, 函数一阶导数函数DIGAMMA,二阶导数函数TRIGAMMA ,误差函数余函数ERFC, 函数自然对数LGAMMA,ORDINAL函数,AIRY 函数,DAIRY函数,Bessel函数JBESSEL,修正的Bessel函数IBESSEL,等等。

二、数组函数

数组函数计算数组的维数、上下界,有利于写出可移植的程序。数组函数包括:

  三、字符函数

较重要的字符函数有:

其它字符函数还有COLLATE,COMPRESS,INDEXC,LEFT,LENGTH,REVERSE,RIGHT,SCAN ,TRANSLATE,VERIFY,COMPBL,DEQUOTE,INDEXW,QUOTE,SOUNDEX,TRIMN,INDEXW。

  四、日期和时间函数

常用日期和时间函数有:

其它日期和时间函数还有DATE、TODAY、DATETIME、DATEJUL、JULDATE、HOUR、MINUTE、SECOND 、TIME、TIMEPART等。详见《SAS系统-Base SAS软件使用手册》、《SAS系统-SAS/ETS软件 使用手册》。

五、分布密度函数、分布函数

作为一个统计计算语言,SAS提供了多种概率分布的有关函数。分布密度、概率、累 积分布函数等可以通过几种统一的格式调用,格式为

CDF计算由'分布'指定的分布的分布函数, PDF计算分布密度函数值,PMF计算离散分布的分布概 率,LOGPDF为PDF的自然对数,LOGPMF为PMF的自然对数。函数在自变量 x处计算,<, 参数表>表示可选的参数表。

分布类型取值可以为: BERNOULLI, BETA, BINOMIAL, CAUCHY, CHISQUARED, EXPONENTIAL, F, GAMMA, GEOMETRIC, HYPERGEOMETRIC, LAPLACE, LOGISTIC, LOGNORMAL, NEGBINOMIAL, NORMAL 或 GAUSSIAN, PARETO, POISSON, T, UNIFORM, WALD 或 IGAUSS, and WEIBULL。 可以只写前四个字母。

例如,PDF('NORMAL', 1.96)计算标准正态分布在1.96处的密度值(0.05844),CDF('NORMAL', 1.96)计算标准正态分布在1.96处的分布函数值(0.975)。PMF对连续型分布即PDF。

除了用上述统一的格式调用外,SAS还单独提供了常用的分布的密度、分布函数。

六、分位数函数

分位数函数是概率分布函数的反函数。其自变量在0到1之间取值。分位数函数计算的 是分布的左侧分位数。SAS提供了六种常见连续型分布的分位数函数。

七、随机数函数

SAS可以用来进行随机模拟。它提供了常见分布的伪随机数生成函数。

1.均匀分布随机数

有两个均匀分布随机数函数:UNIFORM(seed),seed必须是常数,为0,或5位、6位、7位 的奇数。RANUNI(seed),seed为小于2**31-1的任意常数。在同一个数据步中对同一个随机数 函数的多次调用将得到不同的结果,但不同数据步中从同一种子出发将得到相同的随机数序 列。随机数种子如果取0或者负数则种子采用系统日期时间。

2.正态分布随机数

有两种,NORMAL(seed),seed为0,或5位、6位、7位的奇数。RANNOR(seed),seed为任意 数值常数。

3.指数分布随机数

RANEXP(seed),seed为任意数值,产生参数为1的指数分布的随机数。参数为lambda的指 数分布可以用RANEXP(seed)/lambda得到。

另外若Y=alpha-beta*LOG(RANEXP(seed)),则Y为位置参数为alpha,尺度参数为beta的 极值分布。若Y=FLOOR(-RANEXP(seed)/LOG(p)),那么Y是具有参数p的几何分布变量。

4.伽马分布随机数

RANGAM(seed, alpha),seed为任意数值常数,alpha>0,得到参数为alpha的伽马分布 。设X=RANGAM(seed, alpha),则Y=beta*X是形状参数为alpha,尺度参数为beta的GAMMA分布 随机数。如果alpha是整数,则Y=2*X是自由度为2*alpha的卡方分布随机数。

如果alpha是正整数,则Y=beta*X是Erlang分布随机数,为alpha个独立的均值为beta的指 数分布变量的和。

如果Y1=RANGAM(seed,alpha),Y2=RANGAM(seed,beta),在Y=Y1/(Y1+Y2)是参数为(alpha,beta )的贝塔分布随机数。

5.三角分布随机数

RANTRI(seed,h),seed为任意数值常数,0<h<1。此分布在0到1取值,密度在0到h 之间为2x/h,在h到1之间为2(1-x)/(1-h)。

6.柯西分布随机数

RANCAU(seed),seed为任意数值常数。产生位置参数为0,尺度参数为1的标准柯西分布随 机数。Y=alpha+beta*RANCAU(seed)为位置参数为alpha,尺度参数为beta的一般柯西分布随 机数。

7.二项分布随机数

RANBIN(seed,n,p)产生参数为(n,p)的二项分布随机数,seed为任意数值。

8.泊松分布随机数

RANPOI(seed,lambda)产生参数为lambda>0的泊松分布随机数,seed为任意数值。

9.一般离散分布随机数

RANTBL(seed, p1, …, pn)生成取1,2,…,n的概率分别为p1,…,pn的离散分布随机 数。

八、样本统计函数

样本统计函数把输入的自变量作为一组样本,计算样本统计量。其调用格式为“函数 名(自变量1,自变量2,…,自变量n)”或者“函数名(OF 变量名列表)”。比如SUM是求 和函数,如果要求x1,x2,x3的和,可以用SUM(x1,x2,x3),也可以用SUM(OF x1-x3)。这些 样本统计函数只对自变量中的非缺失值进行计算,比如求平均时把缺失值不计入内。

各样本统计函数为:

  • MEAN 均值
  • MAX 最大值
  • MIN 最小值
  • N 非缺失数据的个数
  • NMISS 缺失数值的个数。
  • SUM 求和
  • VAR 方差
  • STD 标准差
  • STDERR 均值估计的标准误差,用STD/SQRT(N)计算。
  • CV 变异系数
  • RANGE 极差
  • CSS 离差平方和
  • USS 平方和
  • SKEWNESS 偏度
  • KURTOSIS 峰度
  • 注意:数据集的存储一般是每行为一个个体的观测值,每列是个体的一个属性(变量), 所以统计一般应该对列进行,而不是象这里对行进行,把各变量作为一个样本的各个观测处 理。这里提供的函数主要用于进行一些自编程的计算。

    SAS/IML矩阵功能简介

    SAS/IML是SAS提供的一个可以进行矩阵运算编程的工具,详细使用请参见有关资料或 系统帮助(Help | Extended Help | SAS System Help: Main Menu | Help for SAS Products | SAS/IML)。它可以用来进行交互的矩阵运算,也可以编好一个程序再一起运行。程序可 以使用分支、循环、模块化子程序等控制结构。数据步中的函数大都能在SAS/IML中使用,SAS/IML 也提供了一些特有的函数。SAS/IML的一个方便之处是它可以直接读取SAS的数据集并把结果 写成SAS数据集,它也有存取外部文件的功能。

    要交互运行SAS/IML,只要在程序窗口输入

    proc iml;
      reset print;
    

    提交此程序,就可以进入交互的SAS/IML运算状态。退出用QUIT语句。SAS/IML中可以使用 标量、行向量、列变量和矩阵,可以使用字符型数据。变量取名规则遵循SAS语言的统一规定 ,变量可以存储标量、向量和矩阵。

    赋值用等号。例如:

    sc = 15.25;
    vh = {1 2};
    vh1=5:9;
    vv = {3, 4};
    mat1 = {1 2 3,
           4 5 6};
    mat2 = {"Li"  "Ming",
           "Zhang"  "Chong"};
    

    我们注意赋值语句是一个SAS语句,它以分号结尾。上面定义了标量sc,行向量vh和vh1, 列向量vv,两行两列的矩阵mat1,字符型矩阵mat2。 由上可见,写矩阵常量时,行的元素之 间以空格分隔,行之间以逗号分割。可以用“开始:结尾”的写法生成一个等差数列行向量。

    矩阵之间可以用<、=等符号进行元素两两间的比较。要得到一个标量的结果,可 以用ALL()函数表示自变量的各元素均为真(非零),用ANY()函数表示自变量的元素中至少 一个为真。可以用&、|、^连接两个逻辑型矩阵(元素间的与、或、非)。

    用||表示矩阵左右连接,用//表示矩阵上下连接。x`表示x的转置。

    SAS/IML中可以进行通常的矩阵加减乘(+、-、*)运算,也可以进行对应元素之间 的乘除(#、/)运算。矩阵加减必须两个矩阵大小完全相等,或者其中一个是标量。矩阵乘 法要求第一矩阵的列数等于第二矩阵的行数,或其中一个是标量。矩阵元素之间的乘除运算 是对应元素进行乘除,当两个矩阵大小完全相同时可以进行运算,其中有一个是标量时可以 进行运算,另外,如果其中有一个是行(列)向量而其长度与另一个矩阵的列数(行数)相 同也可以进行运算。矩阵逆要用INV()函数运算。

    为了读入一个数据集,先打开数据集,用如

    USE  sasuser.c9501;
    

    然后用READ ALL VAR{ 变量列表}的格式读入数据集的各变量值,例如:

    READ ALL VAR {name math chinese};
    print name math chinese;
    

    这时三个变量都可以作为列向量来使用。

    SAS语言的数据管理功能

    前面说过,SAS语言是一种专用的数据管理、分析语言,它提供了很强的数据操作能 力。这些数据操作能力表现在它可以轻易地读入任意复杂格式的输入数据,并可以对输入的 数据进行计算、子集选择、更新、合并、拆分等操作。另外,SAS系统还提供了用来访问其它 数据库系统如Sybase、Oracle的接口,访问各种微机用数据库文件如FoxPro、Excel的接口及 向导,并提供了一个SQL过程来实现数据库查询语言SQL的功能。

    SAS语言直接、间接用于数据管理的语句很多,这里只能拣最常用的介绍。

    SAS数据步的运行机制

    SAS语言的自编程计算功能主要在数据步实现,一个SAS数据步相当于一个单独运行的 程序。但是,SAS语言又是一个专用数据处理语言,所以SAS数据步有其它语言所没有的特点 。我们以下面的简单例子说明这一点:

    data a;
      put x= y= z=;
      input x y;
      z=x+y;
      put x= y= z=;
      cards;
    10 20
    100 200
    ;
    run;
    
    运行后在LOG窗口显示如下记录:
    
    ……
    
    X=. Y=. Z=.
    X=10 Y=20 Z=30
    X=. Y=. Z=.
    X=100 Y=200 Z=300
    X=. Y=. Z=.
    NOTE: The data set WORK.A has 2 observations and 3 variables.
    ……
    

    这个程序的运行流程是这样的:

    提交PROC PRINT;RUN;就可以显示此数据集的内容如下:

                                       OBS     X      Y      Z
     
                                        1      10     20     30
                                        2     100    200    300
    
    

    从这个例子可以看出SAS数据步程序和普通程序的一个重大区别:SAS数据步如果有数据输 入,比如用INPUT、SET、MERGE、UPDATE、MODIFY等语句读入数据,则数据步中隐含了一个循 环,即数据步程序执行到最后一个语句后,会返回到数据步内的第一个可执行语句开始继续 执行,直到读入数据语句(INPUT、SET、MERGE、UPDATE、MODIFY等)读入了数据结束标志为 止才停止执行数据步,并把读入的各个观测写入在DATA语句中指定的数据集。如果没有数据 输入而只是直接计算,则数据步程序不需要此隐含循环。数据步因为有这样一个隐含循环, 所以也提供了用来查询某一步是第几次循环的特殊变量 _N_,它的值为数据步循环计数值。 数据步流程见图 1。

    图 1 数据步流程图

    用INPUT语句输入数据

    在数据步中输入数据可以从原始数据输入,也可以从已有数据集输入。从原始数据输 入要使用INPUT语句来指定输入的变量和格式。数据行写在CARDS语句和一个只有一个顶头的 分号的行之间。

    最简单的INPUT语句使用自由格式:按顺序列出每个观测的各个变量名,中间用空格分开 。变量如果是字符型的需要在变量名后面加一个$符号,$符与变量名可以直接相连也可以隔 一个空格。例如:

    data c9501;
      input name $ sex $ math chinese;
      cards;
    李明 男 92 98
    张红艺 女 89 106
    王思明 男 86 90
    张聪 男 98 109
    刘颍 女 80 110
    ;
    run;
    

    注意这个例子的数据有五个观测,四个变量,每行数据的各变量之间用空格分隔。为输入 这些数据,INPUT语句中依次列出了四个变量名,并在字符型变量NAME和SEX后加了$符。要生 成一个数据集这是最简单的写法。

    使用自由格式也有一些限制条件,如果不满足这些条件时需要改用其它输入格式:

    在满足以上条件时就可以使用自由格式,它也有明显的优点:使用简单;输入数据时不必 上下对齐;不需要知道每个变量的具体列数而只需知道它的次序。

    如果各数据行的各个数据项是上下对齐的,还可以使用INPUT语句的列方式。这时,除了 在INPUT关键字后面列出变量名外,还需要在每个变量名(及$符)后面列出该变量在数据行 中所占据的列起始位置与结束位置,比如上面的例子可以改写成:

    data c9501;
      input name $ 1-10 sex $ 11-13 math 14-16 chinese 18-20;
      cards;
    李明      男  92  98
    张红艺    女  89 106
    王思明    男  86  90
    张聪      男  98 109
    刘颍      女  80 110
    ;
    run;
    

    使用列方式时一定要正确数出每一项所占的位置。列方式有如下特点:

    列方式不要求数据项之间分开,所以经常用来输入紧缩格式的数据。比如,我们要输入一 批身份证号码,但只输入其中的出生年、月、日信息,就可以用如下程序:

    data pids;
      input year 7-8  mon 9-10  day 11-12;
      cards;
    110103751209223
    110101690215005
    ;
    run;
    

    列格式可以与自由格式混用,见1.1.3的例子。

    如果需要完全原样地输入字符型数据(包括头尾空格、单独的小数点),可以用有格 式输入,即在字符型变量名和$符后加上一个输入格式如CHAR10.表示读入10个字符。

    有特殊格式的数据需要用有格式输入,即在变量名后加格式名。其中最常见的是用来 输入日期。数据中的日期写法经常是多种多样的,比如1998年10月9日可以写成“1998-10-9 ”,“19981009”,“9/10/98”等等,为读入这样的日期数据就需要为它指定特殊的日期输 入格式。另外,日期数据在SAS中是按数值存储的,所以如果要显示日期值,也需要为它指定 特殊的日期输出格式。例如:

    data a;
      input date yymmdd8. sales;
      format date yymmdd10.;
      cards;
    56-6-13    1100
    67.12.15    1200
    78 10 2    1300
    891001     1400
    19960101   1500
    20020901   1600
    ;
    run;
    proc print;run;
    

    其中日期数据占据8列位置,如果不满8列要用空格补充,不能让后面的数据进入这8列。 这样可以输入没有世纪数,年、月、日之间用减号、小数点、空格分隔的日期,可以输入YYMMDD 格式的六位数的日期(一位数的月、日前面补0),可以输入带世纪数的YYYYMMDD格式的日期 (一位数的月、日前面补0)。FORMAT语句规定输出日期变量时使用的显示格式。结果为:

                                       1     1956-06-13     1100                 
                                       2     1967-07-11     1200
                                       3     1978-10-02     1300
                                       4     1989-10-01     1400
                                       5     1996-01-01     1500
                                       6     2002-09-01     1600
    

    用YYMMDD10.输入格式可以输入带世纪数的中间有分隔符或无分隔的日期,如:

    data b;
      input date yymmdd10. sales;
      format date yymmdd10.;
      cards;
    56-6-13    1100
    67.12.15    120078 10 2    1300
    891001     1400
    19960101   1500
    20020901   1600
    1956-6-13  1100
    1967.12.15  1200
    1978 10 2  1300
    19891001   1400
    19960101   1500
    20020901   1600
    ;
    run;
    proc print;run;
    

    如果日期变量不是第一个,则它的前一项最好使用列格式并且指定结束列号为日期值 的前一列,或者前一项也使用指定输入格式的输入方法,并且使前一项的输入域宽占满日期 前的列。如果用自由格式则当前一项与日期数据之间间隔超过一个空格时有可能导致日期读 入时对不准位置。如果数据是按列对齐的,还可以在日期变量前加上“@开始列号”说明日期 变量开始读取的位置,比如:

    data b;
      input sales @15  date yymmdd10. ;
      format date yymmdd10.;
      put date=;
      cards;
    1100            56-6-13
    1200            67.12.15
    ;
    run;
    

    数据中日期是从第15列开始的。如果在上面去掉“@15”,则读入的DATE变量为缺失值。 但是如果把日期值与前一个数据值只隔开一个空格则可以用自由格式。

    有格式读取还可以与自由格式结合起来使用,在INPUT语句中指定"变量名 : 格式" 表示按位置读取当前第一个非空列开始的值并用指定的输入格式转换, 比如,中间有日期又没有对齐时就可以用下例这样的方法读取:

    data b;
      input sales date : yymmdd10. ;
      format date yymmdd10.;
      put date=;
      cards;
    1100    56-6-13
    1200       67.12.15
    ;
    run;
    

    INPUT语句有十分复杂的使用方法,可以处理几乎是任意复杂的数据输入问题,这里 我们就不详细讲了,有兴趣的读者可以参考《SAS系统-Base SAS软件使用手册》。

    读入外部数据

    一、文本格式的数据文件

    对于小量的数据,用CARDS语句和空语句把数据夹在中间放在数据步程序中就可以用INPUT 语句输入数据。如果数据量很大(有时可以有上亿行、几千列),直接把数据放在程序中不 利于程序和数据的维护。这时,一种办法是把原始数据放在一个普通的文本格式的文件中, 然后用INFILE语句指定输入文件名。例如,我们可以把1.1.3中的数据行单独生成一个文本文 件stud.txt,假设放在了C:\SAS中,可以用如下程序读入文件中的数据并生成数据集:

    data c9501;  
      infile 'c:\sas\stud.txt';  
      input name $ 1-10 sex $ math chinese;
    run;
    proc print;run;
    

    注意INFILE语句要写在INPUT语句之前,有INFILE语句就不再有CARDS语句和空语句。INFILE 关键字后面跟的是一个包含文件名的字符串,可以使用全路径名,如果只有文件名则在当前 工作目录寻找。

    二、微机格式的数据文件

    SAS还可以读入其它格式的文件,比如FoxPro、Excel等微机格式数据文件。这样 的读入不必编程,而可以使用SAS系统File菜单中的Import命令完成。虽然这不是SAS语言的 内容,但因为实际工作中我们经常会需要读入这样的数据文件,所以我们在这里加以简单介 绍。

    新版SAS提供了一个用于把其他格式的数据文件转换为SAS数据集的导入向导(import wizard) 。在SAS/Base的支持下可以转换用分隔符分隔的文件,用逗号分隔的文件,用制表键分隔的 文件,用户自定义的格式。在SAS/ACCESS关于PC文件的接口的支持下可以转换dBase,Excel ,Lotus文件等特殊格式的文件。导入向导可以从程序编辑窗口启动。例如,我们有一个Excel5 文件(如果是Excel97文件需要在Excel中另存为Excel 5.0/95格式)在 C:\LDF\SAS\NEWBOOK\C9501.XLS中(见图 2):

    为了由它转换得到SAS数据集,在程序窗口中启动File菜单,选“Import”, 这时出现一个选择文件类型的画面:

    选中标准文件格式(Standard file format),并单击 向下箭头打开一个下拉列表,从中选“Excel 5 or 7 Spreadsheets(*.xls)”,按Next钮继 续,出现一个选择文件名的画面,可以在文本框中直接输入Excel文件的全路径名,或按Browse 钮从目录中选取文件。继续后出现选择目标位置的画面,这是要求输入一个结果数据集的名 字和数据库位置,数据库已选WORK我们可以不变,在数据集名处输入C9501A,按Finish钮可 以生成数据集WORK.C9501A。

    三、与大型数据库的接口

    SAS提供了两种办法可以访问大型数据库。SAS/ACCESS可以直接连接Oracle、Sybase 、SQL Server等大型数据库。为了访问储存在这些数据库中的表,需要对数据库中的表在SAS 中建立访问描述文件(access descriptor),和视图描述文件(view descriptor)。例如 ,在数据库服务器DBIN中有一个数据库Finance,其中有一个表Sales,用户名guest用密码anyone 可以访问此库,就可以用以下程序在SAS中建立访问描述文件和视图文件:

    PROC ACCESS DBMS=SYBASE;
        CREATE sasuser.sales.ACCESS;
        SERVER='DBIN';
        DATABASE='Finance';
        TABLE='Sales';
        USER='guest';
        PASSWORD='anyone';
        CREATE sasuser.salesall.VIEW;
        SELECT ALL;
    RUN;
    
    

    其中大写的部分是固定的。这段程序首先生成了访问描述文件SASUSER.SALES.ACCESS,然 后由此访问描述文件生成了视图文件SASUSER.SALESALL.VIEW。在SAS中视图文件和数据集的 使用是一样的,可以使用数据集的地方都可以使用视图文件。

    对于SAS没有直接支持的数据库管理系统,我们在MS Windows下总可以使用ODBC数据 库接口来连接到数据库,这要求我们在安装SAS时安装了ODBC驱动。为了访问外部数据库,首 先要在计算机上安装该数据库的客户端驱动程序,然后在Windows的控制面板中打开ODBC的控 制,新建一项ODBC数据源,输入该数据库管理器的名字,数据库名。然后,使用PROC SQL程 序来建立视图我们以前的数据库为例,假设建立了ODBC数据源finodb:

    PROC SQL;
        CONNECT TO ODBC (DSN='finodb'  UID='guest'  PWD='anyone');
        CREATE VIEW sasuser.sales2 AS
            SELECT * FROM CONNECTION TO ODBC (
                 SELECT * FROM Sales );
    QUIT;
    
    

    其中的CONNECT TO语句用来建立到数据库的连接,后面的CREATE VIEW语句建立一个视图 ,但是图的数据来源是ODBC数据源,所以AS后面是关键字SELECT * FROM CONNECTION TO ODBC ,然后在括号中给出了具体的从外部数据库中取得一个查询结果的SQL语句。这个SQL语句可 以用来取表的一个子集构成视图。生成的SASUSER.SALES2是一个SAS视图,在访问时将用保存 的连接参数临时去访问外部数据库来得到数据。

    这种使用PROC SQL直接连接外部数据库的方法也适用于非ODBC的数据源。

    数据集的复制与修改

    前面讲述了如何从原始数据生成SAS数据集。我们还可以用SET语句把一个已有数据集 复制到一个新数据集,同时还可以进行修改。

    比如要把数据集WORK.C9501复制为数据集SASUSER.CLS,只要用如下程序:

    data sasuser.cls;
      set c9501;
    run;
    

    这样的程序流程中也有一个隐含循环,程序在数据步内反复循环,直到输入数据集C9501 最后一个观测读过。

    我们还可以用SAS程序语句对生成的数据集进行修改。比如,我们把超过100分的语文 成绩都改为100分,就可以用如下程序:

    data c9501a;
      set c9501;
      if chinese>100 then chinese=100;
    run;
    

    当然,这种修改也可以在读入原始数据的数据步中使用而不限于使用SET的数据步。也可 以生成新的变量。

    在数据步中可以用KEEP语句或DROP语句指定要保留的变量或要丢弃的变量。比如,

    data c9501b;
      set c9501;
      keep name avg;
    run;
    

    生成的数据集C9501B只包含NAME和AVG两个变量。用KEEP语句指定要保留的变量。用DROP 语句指定要丢弃的变量,比如上例中的KEEP语句可以换成:

      drop sex math chinese;
    

    用这种方法可以取出数据集的一部分列组成的子集。

    也可以指定一个条件取出数据集的某些行组成的子集。比如,我们希望取出数学分数90 分以上,语文分数100分以上的学生的观测,可以用如下的“子集IF语句”:

    data c9501c;
      set c9501;
      IF math>=90 and chinese>=100;
    run;
    

    注意子集IF语句不同于我们前面所讲的分支语句,它没有THEN部分,只有条件,用于取出 满足条件的行子集。

    用SET和OUTPUT语句拆分数据集

    有时我们需要根据某一分类原则把数据行分别存放到不同的数据集。比如,我们希望 把数据集C9501中的所有男生的观测放到数据集C9501M中,把所有女生的观测放到C9501F中, 可以使用如下程序:

    data c9501m c9501f;
      set c9501;
      select(sex);
        when('男') output c9501m;
        when('女') output c9501f;
        otherwise put sex= '有错';
      end;
      drop sex;
    run;
    proc print data=c9501m;run;
    proc print data=c9501f;run;
    
    

    这个程序中有两个地方需要注意:在DATA语句中,我们指定了两个数据集名,这表示要生 成两个数据集。程序中用SET语句引入了一个数据集,这个数据集的观测如何分配到两个结果 数据集中呢?关键在于OUTPUT语句。OUTPUT语句是一个可执行语句,它使得当前观测被写到 语句指定的数据集中。这样,我们根据SELECT的结果把不同性别分别放到了两个不同数据集 中。

    OUTPUT语句还可以用来强行写入数据集而不必象我们在数据步流程图中说明的那样等 到数据步最后一个语句完成。数据步中有了OUTPUT语句后数据步流程中不再有自动写入观测 的操作,而只能由OUTPUT语句指定输出。不指定数据集名的OUTPUT语句输出到第一个结果数 据集。比如下面的程序生成一个包含1到10的及其平方的有10个观测的数据集:

    data sq;
      do i=1 to 10;
        j=i*i;
        output;
      end;
    run;
    proc print;run;
    

    如果删去上面的OUTPUT语句则结果数据集中只有i=11,j=100的一个观测。

    数据集的纵向合并

    几个结构相同的数据集可以上下地连接到一起。比如,我们有四个班的学生情况的数 据集Class1-Class4,每个数据集包含一个班学生的学号、姓名、性别信息,我们希望把这些 数据集合并为一个大数据集,可以用如下代码:

    data classes;
      set class1 class2 class3 class4;
    run;
    

    可见,要把若干个结构相同的数据集合并为一个数据集,只要在DATA语句中指定要生成的 大数据集的名字,然后在数据步中使用SET语句并在SET语句中依次列出各小数据集。

    有时我们需要在合并数据集时加入一个变量来指示每一个观测原来来自哪一个小数据 集,这可以在SET语句的每一个数据集名后面加一个括号,里面写上IN=变量名,变量名所给 的变量取1表示观测来自此数据集,取0表示观测非来自此数据集。例如,在2.3.5中我们把C9501 数据集按男、女拆分成了C9501M和C9501F两个数据集并抛弃了性别变量,就可以用如下程序 连接两个数据集并恢复性别信息:

    data new;
      set c9501m(in=male) c9501f(in=female);
      if male=1 then sex='男';
      if female=1 then sex='女';
    run;
    

    在数据步中,如果观测来自C9501M,则变量MALE值为1,如果观测来自C9501F则变量FEMALE 值为1,可以使用这两个变量的值定义新变量SEX。用数据集选项的IN=指定的变量不能直接进 入结果数据集而只能用于数据步程序中。

    数据集的横向合并

    两个(或多个)数据集如果包含了同样的一些观测的不同属性(变量),比如,数据 集C9501U包含学生的姓名、性别,数据集C9501V包含学生的数学成绩,数据集C9501W包含学 生的语文成绩,且各数据集的观测是按顺序一一对应的,就可以用如下带有MERGE语句的数据 步把它们左右横向合并到一个数据集NEW:

    data new;
      merge c9501u c9501v c9501w;
    run;
    

    这样虽然可以横向合并数据集,但是如果各数据集的观测顺序并不一样,就会把不同 人的成绩合并到一起。所以横向合并一般应该采用按关键字合并的办法,即先把每个数据集 按照相同的、能唯一区分各观测的一个(或几个)变量排序,然后用BY语句和MERGE语句联合 使用,这样即使原来观测顺序不一致也可以保证横向合并的结果没有错。下例先把C9501数据 集横向拆分为包含姓名、性别的数据集C9501X和包含姓名、数学成绩、语文成绩的数据集C9501Y ,然后按关键字横向合并:

    data c9501x;
      set c9501;
      keep name sex;
    run;
    data c9501y;
      set c9501;
      keep name math chinese;
    run;
     
    proc sort data=c9501x;
      by name;
    run;
    proc sort data=c9501y;
      by name;
    run;
    data new;
      merge c9501x c9501y;
      by name;
    run;
    proc print;run;
    

    其中的PROC SORT是排序过程,用来把数据集按照某个变量的次序排序(这里是按变量NAME 的次序排列,用BY语句指定排序的变量名)。

    用UPDATE语句更新数据集

    如果我们发现数据集中的某些数据值有错误或者现在的值已经改变了,我们可以从更 正了的原始数据重新生成数据集,或者使用更有效的方法,即建立一个只包含新数据值的数 据集,用此数据集修改原数据集。使用如下的DATA步中可以实现数据集的更新:

    		DATA  新数据集名;
    			UPDATE  原数据集  更新用数据集;
    			BY  关键变量;
    		RUN;
    
    

    例如,比如我们发现数据集C9501中王思明的语文成绩实际应该是91分,张红艺性别应为 男,可以先生成如下的只包含更正数据值的数据集,不需要改的观测不列入,不需要改的变 量不列入或取缺失值:

    data upd;
      input name $ sex $ chinese;
      cards;
    张红艺 男 .
    王思明 . 91
    ;
    run;
    

    然后,把原数据集C9501和更新用数据集UPD均按姓名(NAME)排序:

    proc sort data=c9501;
      by name;
    run;
    proc sort data=upd;
      by name;
    run;
    

    最后用UPDATE和BY更新得到新数据集NEW,其中王思明的语文成绩改成了91分,张红艺性 别改成了男。

    data new;
      update c9501 upd;
      by name;
    run;
    proc print;run;
    
    但是,这个新数据集中有一个错误:王思明的语文成绩修改以后他的平均分也应作相应改动。 所以此例应改为:
    data new;
      update c9501 upd(in=in_upd);
      if in_upd=1 then 
         avg = math*0.5 + chinese/120*100*0.5;
      by name;
    run;
    proc print;run;
    

    用PROC SQL管理数据

    SAS系统首先是一个数据管理系统,因此它除了可以用SAS语言程序管理SAS数据库、 数据集外,还提供了其它大型数据库管理系统(如Oracle、Sybase)通用的SQL语言功能。在SAS 系统中SQL语言实现在SQL过程中。SAS的SQL过程可以从一个或多个表中查询信息,生成表, 向表中插入行,更新表的内容,对表进行纵向合并、横向连接等等。另外,PROC SQL还可以 直接连接外部数据库,我们已经在2.1.3作了介绍。SQL语言可以实现极其复杂的数据管理功 能,在这里我们只对它的查询功能作简单介绍,感兴趣的读者可以自己阅读一些数据库管理 方面的书籍。

    用PROC SQL作查询的最简单的用法如下:

    	PROC SQL;
    		SELECT  第一项第二项,…,第n项
    		FROM 数据集
    		WHERE  观测选择条件;
    	RUN;
    

    其中SELECT是一个语句,FROM和WHERE叫做子句,注意语句是在最后结尾的,中间没有分 号。SELECT子句中指定的各项一般为变量名,中间用逗号分隔(注意不是用空格分隔)。FROM 子句指定要从哪个数据集查询。WHERE子句指定选择观测的条件。所以,SELECT语句可以很方 便地从一个表查询一个子集,并可以自动输出到输出窗口而不需再使用PROC PRINT。例如, 下面的程序显示语文成绩在100分以上(包含)的学生的姓名和数学成绩:

    proc sql;
      select name, math
      from c9501
      where chinese>=100;
    run;
    

    结果显示

                                         NAME            MATH
                                         --------------------
                                         张红艺            89
                                         张聪              98
                                         刘颍              80
    

    在SELECT语句中还可以加入ORDER BY子句,可以为查询结果排序。比如,下程序

    proc sql;
      select name, math
      from c9501
      where chinese>=100
      order by math desc;
    run;
    

    结果为

                                         NAME            MATH
                                         --------------------
                                         张聪              98
                                         张红艺            89
                                         刘颍              80
    

    SELECT的强大查询功能还表现在它可以从几个表联合查询。比如,考虑2.3.7中的C9501X 和C9501Y,我们要从这两个数据集查询与从C9501一个数据集同样的结果,可以用此程序:

    proc sql;
      select c9501x.name, math
      from c9501x, c9501y
      where c9501x.name=c9501y.name
         and chinese>=100
      order by math desc;
    run;
    

    其中连接两个数据集的办法是在WHERE子句指定C9501X.NAME=C9501Y.NAME这样的连接条件 。在SELECT中指定变量时如果有两个数据集中共有的变量要用C9501X.NAME这样的带有表名( 数据集名)的形式。

    连接的两个表有时是同一个表。比如,我们有几个学生的姓名和生日,希望找出那些 有相同生日的人。可以用如下的SQL过程:

    title '找出生日相同的人';
    data class;
      input name $ 1-8 birth yymmdd10.;
      format birth yymmdd10.;
      label name='姓名'  birth='生日';
      cards;
    李明      78-6-1
    王思明    78-5-19
    张聪      78-6-1
    刘颖      78-10-18
    张红艺    78-5-19
    ;
    proc sql;
      select name, birth
        from class a
        where birth in select birth
          from class b
          where b.name ^= a.name
        order by a.birth;
    run;
    

    结果如下:

                                   找出生日相同的人                             21
     
                                 姓名            生日
                                 --------------------
                                 王思明    1978-05-19
                                 张红艺    1978-05-19
                                 张聪      1978-06-01
                                 李明      1978-06-01
    

    如果我们还希望把查询的结果存入一个数据集,可以在上面的第一个SELECT语句前面加上CREATE TABLE 表名 AS:

    proc sql;
      CREATE TABLE bsame AS
        select name, birth
          from class a
    …………………
    run;
    proc print data=bsame label;
      id name;
      by birth;
    run;
    

    结果如下:

                                   找出生日相同的人                             22
     
    ------------------------------ 生日=1978-05-19 -------------------------------
     
                                         姓名
     
                                        王思明
                                        张红艺
     
     
    ------------------------------ 生日=1978-06-01 -------------------------------
     
                                         姓名
     
                                         张聪
                                         李明
    

    如果不用SQL过程想得到同样的结果,可以使用如下数据步和过程步:

    proc freq data=class noprint;
      tables birth / out=bfreq;
    run;
    proc sort data=class;
      by birth;
    proc sort data=bfreq;
      by birth;
    data bsame;
      merge class bfreq;
      by birth;
      if count>1;
    run;
    proc print data=bsame label noobs;
      var name;
      by birth;
    run;
    

    练习

    1. 用SAS数据步列出10000以下的素数,写出程序。
    2. 生成t分布的双侧分位数表。水平取0.001,0.002,0.005,0.01,0.02,0.05,0.10 ,0.20,自由度取1-100,分位数精确到小数点后3位。表格应为行、列对齐的形式,并有列 标题。写出生成这样的表格并存放到一个文本文件中的SAS程序。

    3. 写出计算从自己生日到2000年初经过的天数的程序。
    4. 下表为某邮购服务部的部分顾客记录:

      姓名

      性别

      地区

      日期

      金额

      章文

      华东

      1996-3-20

      1099

      王国铭

      华东

      1996-5-19

      39

      童子敏

      华北

      1996-1-5

      986

      刘念新

      东北

      1997-10-1

      3581

      李思今

      华北

      1997-4-4

      659

      关昭

      东北

      1996-11-5

      358

      赵霞

      东北

      1998-9-6

      2010

      (1)用数据步把此数据输入到SAS数据集;

      (2)用程序找出男性顾客购买金额超过1000的哪些人;

      (3)把数据拆分为包含姓名、性别、地区的一个数据集和包含姓名、日期、金额的一个 数据集;

      (4)用MERGE和BY合并上一步拆开的两个数据集。