《SAS编程演义》笔记(1-4章)

清歌苦调两不厌:夯实基础

查看SAS安装、许可的模块

1
2
3
4
5
6
7
8
9
proc product_status; /*查看SAS已安装模块*/
run;
proc setinit; /*查看SAS已许可模块*/
run;
/*查看完整安装报告*/
%include "D:\...\sasinstallreporter4u.sas" /*SAS程序文件地址依据存储路径修改*/
%sasinstallreporter; /*查看完整安装报告*/

如何让SAS自动加载永久逻辑库

  1. 如果想要让SAS语句建立的永久库在启动后就能看到,可以把包含LIBNAME语句的程序命名为autoexe.sas,并放入和sas.exe同级的目录中,此后每次SAS启动时会自动运行autosas.exe程序。
  2. 通过工具按钮建立永久库时,可以在建立时勾上Enable at startup,下次启动SAS时就会自动加载这个永久库。

为什么又了数据文件还要视图

视图是依据查询语句动态生成,因此视图本身几乎不占据存储空间。利用视图可以节约硬盘空间。两者在世界内容效果方面是一样的。

日期与时间的本质

  1. 日期实际存储的是距离1960年1月1日的天数
  2. 时间是距离凌晨的秒数

打破SAS命名规则

修改系统选项VALIDMEMNAMEVALIDVARNAME

1
2
3
4
5
6
7
options VALIDMEMNAME=extend VALIDVARNAME=any;
data 中文名演示;
SAS中文变量名 = "YES";
SAS中文變量名 = "YES";
'2SAS中文变量名'n = "YES";
'SAS空格 #@%特殊字符变量名'n = "YES";
run;

运算符

  • 不等于 ^= ~=
  • 两者取小 ><
  • 两者取大 <>
  • 连接运算符 ||
  • 与 & and
  • 或 ! | or
  • 非 ^ ~ not

IF-ELSE配合DO-END

希望执行的不仅仅是一个动作,而是多个动作,此时可以在关键词THEN后面用夹板语句DO-END,把多个动作整合在DO-END中。

1
2
3
4
5
6
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;

循环

  • DO-END
  • DO-WHILE
  • DO-UNTIL
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    data random;
    do i=1 to 10;
    r=rannor(23);
    output;
    end;
    run;
    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;

数组结构

  • 数组中的变量必须有相同的数据类型
  • ARRAY array-name {number-of-elements} <$> <length> <array-elements> <(initial-value-list)>
  • 元素个数可以用{*}代替,让SAS自动计数;可以指定具体的数字,如{7};可以指定一定的数字范围,如{1:7}
  • 元素名可以是变量名也可以是SAS自定义变量,如_ALL_(变量类型需相同,_NUMERIC_,_CHARACTER_,_TEMPORARY_(临时变量)
  • <>中的内容并非必须有。如$和length只有在数组元素为字符型时才用到。

PDV与DATA步自循环

1
2
3
4
5
6
7
8
9
10
11
data demoPDV;
put "第" _n_ "次前运行:" _all_;
input ID $ Chiness Math English;
Sum = Chiness + Math + English;
put "第" _n_ "次后运行:" _all_;
datalines;
S001 80 99 93
S002 90 85 95
S003 83 88 81
;
run;

@与@@

  • 当DATALINES数据行里要读入的数据列数=要读入的变量数,也就是说一行就是一条观测时,无尾。
  • 当DATALINES数据行里要读入的数据列数>要读入的变量数,而且是整数倍时,也就是说一行=K*数条观测(K为>=1的整数),用@@。
  • 当一个DATA步里有多个INPUT语句时,我们需要单尾@。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    * 数据列数=变量数
    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住当前INPUT语句不跳,但若刚好是DATA步最后一个INPUT语句,跳。
    • 二尾(@@)打死都不跳。
    • 最后,无论多少尾,数据行末尾必定自动跳。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
data test;
input x @;
input y;
input @@;
datalines;
1 2 3
4 5 6
7
;
run;
data test;
input x;
input y @@;
input z @;
datalines;
1 2 3
4 5 6
7
;
run;

苔点狂吞纳线青:读取数据

读取方式

  • LIBNAME语句
  • SQL直通设施(SQL pss-through facility)
  • ACCESS/DBLOAD过程,已不推荐
  • IMPORT/EXPORT过程
  • INFILE+INPUT语句
  • INPUT+DATALINES语句
  • IO函数

LIBNAME访问DBMS数据文件

LIBNAME libref engine-name \<SAS/ACCESS-connection-options> \<SAS/ACCESS-LIBNAME-options>;

libname mydb2 db2 user=yuanyi password="wuyuanyi" datasrc=datadb

  • libref: 逻辑库名,其实是存放数据表和视图的DBMS数据库、架构、服务器端别名
  • engine-name: 引擎名,不同的DMBS数据文件需要指定不同的引擎名,入oracle、db2等
  • SAS/ACCESS 连接选项,常用的如用户名,密码等,不同的DMBS数据文件会有所不同
  • SAS/ACCESS 逻辑库选项,控制SAS处理DBMS对象的方式,不同的DBMS数据文件会有所不同

PROC IMPORT语法

PROC IMPORT

DATAFILE = “filename”|DATATABLE = “tablename“(Not used for Microsoft Excel files)

<DBMS = data-source-identifier>

<OUT = libref.SAS data-set-name> \<SAS data-set-option(s)>

<REPLACE>

<file-format-specofoc-statements>;

RUN;

  • filename: 欲读入的文件,包括路径、文件名以及扩展名(如:d:\mydata.xls),当然也可以用之前定义的fileref
  • data source identifier: 一般情况下与文件扩展名一致,.mdb、.dbf、.dat、.sav等。如果是excel文件,建议用Excel
  • <OUT句>: 读入数据后存放的SAS数据库和数据集名称
  • <SAS data-set-option(s)>:对保存的数据进行数据集读写,变量筛选,观测筛选等方面的控制
    • PW=选项加密数据集
    • READ=、WRITE=、ALTER=选项分别加密读、写以及修改权限
    • KEEP=、DROP=、RENAME=选项筛选、重命名变量
    • OBS=、WHERE=筛选保留的观测
  • <REPLACE>: 重复运行时替换掉同名数据集
  • <file-format-specific-statements>: 对分隔符分隔的文件(如CSV文件)、EXCEL文件、Access数据文件、dBase数据文件、Paradox数据文件、SPSS以及Stata数据文件做更详细的读取设置
  • 对于分隔符分隔的文件
    • DATAROW=n 指定数据从第n行开始
    • GETNAMES=YES|NO 应对数据文件首行是不是变量名
    • GUESSINGROWS=n|MAX 应对仅用默认头20行数据无法判断变量类型和长度的情况
    • DELIMITER=‘char’|’nn’x 个性化指定数据文件的分隔符,如DELIMITER=‘#’|‘20’x(十六进制ascii码的空格)
  • 对于EXCEL文件
    • SHEET=EXCEL文件中特定的sheet
    • RANGE=读入某sheet中特定的数据区域

LIBNAME访问PC文件语法

LIBNAME <libref> <engine-name> <‘physical-path and filename.ext’> <SAS/ACCESS-engine-connection-options> <SAS/ACCESS LIBNAME-options>;

  • libref: 逻辑库名,其实是存放数据表和视图的DBMS数据库、架构、服务器端别名
  • engine-name: 引擎名,不同的DMBS数据文件需要指定不同的引擎名,入oracle、db2等
  • ‘physical-path and filename.ext’: PC数据文件地址,文件名以及扩展名,置于引号中
  • SAS/ACCESS 连接选项,常用的如用户名,密码等,不同的DMBS数据文件会有所不同
  • SAS/ACCESS 逻辑库选项,控制SAS处理DBMS对象的方式,不同的DBMS数据文件会有所不同

⚠️SAS在读取Excel文件时,默认用前8行数据判定列的数据类型和长度。解决的办法是让SAS读入更多的数据行再确定数据类型,然而SAS未为Excel提供这样的选项。可以修改Windows注册表的Excel的TypeGuessRows的值为0,这样SAS会在读入所有行后再确定变量的类型。

1
2
3
4
5
6
7
8
9
10
11
filename myexcel "D:\Data\Raw|class.xlsx";
prco import
datafile=myexcel dbms=excel out=myxls replace;
range="'sheet1$A1:E20'n"; /*读入sheet1的A1-E20区域数据*/
getnames=yes;
run;
proc import
datafile=myexcel dbms=excel out=myxls replace;
dbdsopts="firstobs=3 obs=8"; /*读入sheet1的3-8行区域数据*/
getnames=yes;
run;

读入CSV文件

1
2
3
4
5
6
7
filename mycsv "D:\Data\Raw|class.csv";
proc import
datafile=mycsv dbms=csv out=tmp replace;
getnames=yes;
guessingrows=20; /*通过前20行确定变量类型*/
datarow=2; /*数据从第二行开始*/
run;

读入TXT特殊字符分隔的文件

1
2
3
4
5
6
7
8
9
10
11
12
filename mytxttab "D:\Data\Raw|class_tab.txt"
filename mytxtblk "D:\Data\Raw|class_blk.txt"
proc import
datafile=mytxttab dbms=dlm out=tmp replace;
delimiter='09'x;
getnames=yes;
run;
proc import
datafile=mytxtblk dbms=dlm out=tmp replace;
delimiter='20'x;
getnames=yes;
run;

INPUT语句列表读入式

适合数据不齐整

INPUT <pointer-control> variable <$> <:|$|~> <informat.> @|@@;

  • pointer-control: 控制变量读入点起始位置。针对行: #, /。针对列: @, +。
  • variable: 变量列表
  • $: 变量类型为字符型
  • :|$|~: 三个互斥点可选用格式修饰符
    • : 读入点变量列上不齐整,读到空列、读完字符定义的长度或者数据行结尾时就算一个变量读完
    • & 读入点字符变量包含不连续的空格
    • ~ 读入其他分隔符分隔数据,字符变量引号,引号里有分隔符
  • informat.: 输入格式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    data tmp;
    input name : $13. gender $ age location $; /*:表示遇到空格或第13列,才读完name*/
    datalines;
    WYY M 25 Shanghai
    WuYuanyi M 25 Shanghai
    StatsThinking M 1 Beijing
    ;
    run;
    data tmp;
    input name & $9. gender $ age location $; /*一个空格不会认为变量结束*/
    datalines;
    W YY M 25 Shanghai
    Wu Yuanyi M 25 Shanghai
    ;
    run;
    data tmp;
    infile datalines delimiter=',';
    input name & $9. gender $ age location $ affiliation ~ $12.;
    datalines;
    YY WU,M,30,Shanghai,"SHTU,SSIT"
    Yuanyi Wu,M,30,Shanghai,"SHTU,SSIT"
    run;

INPUT语句列读入式

适合数据齐整的情况

INPUT variable<$> start-column<-end-column> <.decimals> @|@@;

  • start-column<-end-column>: 变量读入点起止列
  • .decimals: 小数位数
1
2
3
4
5
6
data tmp;
input name $ 1-9 gender $ 11 age 13-14 location $ 16-23;
YY Wu M 25 Shanghai
Yuanyi Wu M 25 Shanghai
;
run;

INPUT语句格式读入式

可以对字符变量指定宽度,以及读入非标准形式对数字,如科学计数法、带货币符号、带千分位等。

INPUT <pointer-control> variable informat. @|@@;
INPUT <pointer-control> (variable-list) (informat-list) @|@@;
INPUY <pointer-control> (variable-list) (<n> informat.) @|@@;

  • (variable-list) (informat-list): 变量格式列表
    • 编号范围列表:形如x1,x2,x3可缩写为x1-xn
    • 名称范围列表:x–a定义顺序从x到a的所有变量,x-numeric-a为x到a的所有数值型变量,x-character-ax到a的所有字符型变量
    • 名称前缀列表:如x_1,x_2或x1,X2可简写成x:
    • 特殊SAS名列表:_numerica_, _character_, _all_
  • <n> informat.: 变量格式列表n表示格式重复的次数
1
2
3
4
5
6
7
8
data tmp;
/*gender虽然只有1列,但声明时要带上其后的一个空格,age,location类似*/
input name & $10. gender $2. age 2. +1 location $8. fee comma6.;
datalines;
YY Wu M 25 Shanghai 12,345
Yuanyi Wu M 25 Shanghai 54,321
;
run;

INPUT语句命名读入式

INPUT <pointer-control> variable= <$> @|@@;
INPUT <pointer-control> variable= informat. @|@@;
INPUT variable= <$> start-column<-end-column> <.decimals> @|@@;

1
2
3
4
5
6
7
data tmp;
input name= $ gender= $ age= location=$;
datalines;
name=WYY gender=M age=25 location=Shanghai
name=GY gender=F age=24 location=Shanghai
;
run;

数据导出

PROC EXPORT DATA=<libref.>SAS data set<(SAS data set options)>

OUTFILE=”filename”|OUTTABLE=”tablename”

<DBMS=data-source-identifier\>

<LABEL>

<REPLACE>

<file-format-specific-statements>;

RUN;

  • SAS data set: 欲输出的数据集
  • filename|tablename: 输出文件的地址名称
  • LABEL: 输出文件中输出变量名的标签
1
2
3
4
5
6
7
8
9
proc export
data=sashelp.class outfile='D:\Data\Raw|class.xlsx'
dbms=excel replace;
run;
proc export
data=sashelp.class outfile='D:\Data\Raw|class_tab.txt'
dbms=csv replace delimiter='09'x;
run;

行舟来去泛纵横:变量观测

DATA+SET语句

DATA <data-set-name-1 <data-set-options-1>> <…data-set-name-n <data-set-options-n>> </<DEBUG> <NESTING> <STACK = stack-size>> <NOLIST>;

DATA _NULL_ </<DEBUG> <NESTING> <STACK = stack-size>> <NOLIST>;

  • data-set-name: 欲创建的SAS数据集名
  • data-set-options: 欲创建的SAS数据集的限制选项
  • DEBUG: 调试程序逻辑错误以及部分数据错误
  • NESTING: 日志中标记DO-END和SELECT-END语句的起止
  • STACK =: 可嵌套LINK语句个数
  • NOLIST: 出错时不在日志打印每个变量的值
  • _NULL_: 不建任何SAS数据集

获取背后数据

  1. 利用OSD TRACE ONODS TRACE OFF对整个统计过程进行全程追踪和监控:
1
2
3
4
5
6
ods trace on;
proc lifetest data=sashelp.bmt;
time t*status(0);
strata group;
run;
ods trace off;
  1. 在LOG文件中,产生的结果一目了然,想要什么数据随意抓取。
  2. ODS OUTPUT语句抓取Result树形目录下结果:
1
2
3
4
5
ods output SurvialPlot=SurvPlotData;
proc lifetset data=sashelp.bmt;
time t*status(0);
strata group;
run;

IF与WHERE

  1. WHERE更高效。WHERE语句在读入PDV之前就先行判断,求子集IF语句先读入观测进入PDV,而后再判断。
  2. WHERE更广泛。WHERE语句可用在DATA步,PROC步,作为数据集选项使用。IF语句只能作为DATA步语句使用。
  3. IF语句对INPUT语句创建的观测有效,WHERE语句只能筛选数据集里的观测。
  4. 当有BY语句时,求子集IF语句与WHERE语句结果可能不同,因为SAS创建BY组再WHERE语句之后,求子集IF语句之前。
  5. 求自己IF语句可以用在条件IF语句中,WHERE语句不行
  6. 当读入多个数据集时,WHERE语句可以针对每个数据集单独筛选,求子集IF语句不行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*打开数据集时,直接读取只需要的观测:通过WHERE选项限定读入数据集*/
data tmp;
set sashelp.class(where=(sex="F"));
run;
/*PDV里筛选过滤观测*/
/*通过WHERE语句*/
data tmp;
set sashelp.class;
where sex="F";
run;
/*通过求子集IF语句*/
data tmp;
set sashelp.class;
if sex="F";
run;
/*只写入所需观测进入数据集:通过WHERE选项限定输出数据集*/
data tmp(where=(sex="F"));
set sashelp.class;
run;
data want(where=(not missing(id)));
set raw1(where=(age between 20 and 30)) raw2(where=(sex="F"));
run;

KEEP、DROP与RENAME

  1. 选项只作用于被其尾随的数据集,而语句作用域PDV,即语句对不管来自哪个数据集的变量都有效。
  2. KEPP/DROP,RENAME,WHERE和其他语句的执行有固定的执行顺序。
    • 数据集读入和输出阶段的执行顺序:
      1. KEPP/DROP选项筛选变量
      2. RENAME选项修改变量名
      3. WHERE选项筛选观测
    • 编程处理阶段:
      1. WHERE选项筛选观测
      2. 其他执行语句
      3. KEPP/DROP选项筛选变量
      4. RENAME选项修改变量名

编译变量和临时变量

编译变量(NOBS、IN、OBS、END、POINT等)和临时变量的赋值方式是将等号左边赋值给等号右边,其他赋值语句是将等号左边赋值给右边,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*PROC SQL获取观测值 最慢*/
proc sql;
select count(name) as N from sashelp.class;
quit;
/*_N_获取观测数 中*/
data _null_;
set sashelp.class end=last;
if last then put _n_;
run;
/*编译变量获取观测数 最快*/
data _null_;
if 0 then set sashelp.class nobs=n;
put _all_;
run;

CALL例程

CALL例程也可以生成新变量,这种方法是函数形式的一种变种。

1
2
3
4
5
6
7
8
9
10
data rand;
seed=123;
do i=1 to 10;
x=rannor(seed);
call rannor(seed,y);
output;
end;
run;
proc print data=rand;
run;

变量类型转换

new_variable = input(original_variable, informat.);

new_variable = put(original_variable, format.);

  • new_variable:欲生成的新变量
  • informat.:变量输入格式
  • format.:变量输出格式

DATA步实现累加

累加语句:variable+expression;

  • variable:累加器变量,必须为数字型变量,累加后的值保存于此变量中
  • expression:表达式,表达式的值会被累加到累加器变量variable中
1
2
3
4
5
6
data want;
set sashelp.class;
cum_weight+weight;
cnt_weight+1;
avg_weight=cum_weight/cnt_weight;
run;

RETAIN语句:RETAIN <element-list(s) <initial-value(s)|(initial-value-1)|(initial-value-list-1)> <…element-list-n <initial-value-n|(initial-value-n)|(initial-value-list-n)>>>

  • element-list:需要保留PDV里的值的参数,可以是变量名、变量列表、数组名
  • initial-value:初始值,对应于只有一个参数或者参数列表里的第一个参数
  • (initial-value-1):带括号的初始值
  • (initial-value-list-1):带括号的初始值列表
1
2
3
4
5
6
7
data want;
set sashelp.class;
retain cum_weight cnt_weight (0 0);
cum_weight=cum_weight+weight;
cnt_weight=cnt_weight+1;
avg_weight=cum_weight/cnt_weight;
run;

BY语句的作用是在PROC SORT中排序后,在SET、MERGE、UPDATE以及MODIFY语句中,指定分组变量,产生临时变量FIRST.变量和LAST.变量用以标示观测变量是否为分组变量里的第一条和最后一条。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
proc sort data=sashelp.class out=class;
by sex;
run;
data want;
set class end=last;
by sex;
if first.sex then do;
cum_weight=0;
cnt_weight=0;
end;
cum_weight+weight;
cnt_weight+1;
if last.sex then do;
avg_weight=cum_weight/cnt_weight;
output;
end;
run;

隔行取数据

SAS DATA步是隔行处理数据,一旦一行处理完,读入下一行数据后,前面行的数据就不在PDV里了。

  • LAG:把前面任一行的数据在取出来
  • DIF:计算目前行与前面任一行的差
1
2
3
4
5
6
7
8
9
10
11
data want;
set sashelp.class;
/*前面2人是谁*/
pre1name=lag1(name);
pre2name=lag2(name);
/*与前面两人身高的差*/
dif1height=dif1(heigh);
dif2hegith=dif2(height);
run;
proc print'
run;