《SAS编程演义》笔记(9-10章)

拙中藏巧混天成:统计表格

医药科研

  • 基线信息表格:介绍人群基本情况
  • 危险因素表格:会在表格中给出事件频数和百分比、粗的效应值及其95%CI和P值
  • 结局效应表格:横款目为终点指标,纵款目除了分组信息,还有效应量
  • 亚组分析表格:最重要的信息是需要提供交互作用校验的结果,通常以森林图的形式展示

统计汇总过程

一个统计过程中只能获得一种变量(分类、连续)的统计数据,款目形式不便统一。

  • RTF(Rich Text File)可直接用Word打开编辑
  • journal3a是最贴近三线表的样式
1
2
3
4
5
6
7
8
9
10
11
12
ods tagsets.rtf file="table.rtf" style=journal3a; /**/
proc freq data=sashelp.heart;
table bp_status*sex / nopercent norow;
run;
ods tagsets.rtf close;
ods tagsets.etf file="table2.rtf" style=journal3a;
proc means data=sashelp.heart mean std maxdec=2;
class sex;
var height;
run;
ods tagsets.rtf close;

PROC TABULATE

能一次性实现多种变量的统计需求,也能比较灵活的组合表格形式,但是仍达不到标准三线表的要求,而且也无法计算统计量和P值。

  • CLASS语句申明分类变量
  • VAR语句申明分析变量
  • TABLE语句设置表格形式,逗号“,”作为页、行、列等纬度的间隔符,星号“*”作为变量与其显示格式、统计关键字的关联符,括号“()”用来强制分组。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ods tagsets.rtf file="table4.rtf" style=journal3a;
title "Table 1. Blood Pressure and Height by Sex";
proc tabulate data=sashelp.heart;
class sex bp_status;
var height;
table sex, bp_status*(n rowpctn) height*(mean std);
run;
ods tagsets.rtf cloase;
ods tagsets.rtf file="table5.rtf" style=journal3a;
title "Table 1. Blood Pressure and Height by Sex";
proc tabulate data=sashelp.heart;
class sex bp_status;
var height;
table bp_status*(n colpctn) height*(mean std), sex;
run;
ods tagsets.rtf cloase;

PROC REPORT

纵款目无法用分组变量,横款目值没有缩进,统计变量之间没有连字符,无法获得检验统计量和P值。

1
2
3
4
5
6
7
8
9
10
ods tagsets.rtf file="table6.rtf" style=journal3a;
ods escapechar='^';
title "Table 6, Height and Weight by Gender";
proc report data=sashelp.class nowd;
column sex height, ("^R'\brdrb\brdrs'" mean std) weight, ("^R'\brdrb\brdrs'" mean std);
define sex/group;
define height/analysis format=5.2;
define weight/analysis format=5.2;
run;
ods tagsets.rtf close;

完美三线图

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/*1 血压状态数据整理*/
/*1.1 获取频数百分比*/
proc freq data=sashelp.heart;
table bp_status*sex /missing nopercent outpct out=desc1;
run;
data desc1;
set desc1;
length value $25;
value = compress(put(count, 6.)) || ' (' || compress (put(pct_col, 4.1)) ||')';
run;
/*1.2 转置,性别作为列*/
proc transpose data=desc1 out=desc1(drop=_name_) prefix=col;
var value;
by bp_status;
id sex;
run;
/*1.3 获取卡方检验P值*/
proc freq data=sashelp.heart;
table bp_status*sex / norow nocol nopercent chisq;
output out=pvalue1(keep=p_pchi rename=(p_pchi=pvalue)) pchi;
run;
/*1.4 合并变量标签,描述数据,P值*/
data dslabel1;
set pvalue1;
length label $ 85;
label=cats("^S={font_weight=bold}", "Blood presure status"); /*RTF代码,加粗标签*/
run;
data merge1(keep=label col: pvalue);
set dslabel1 desc1;
if _n_>1 then label="^{nbspace 6}" || bp_status; /*RTF代码,增加缩进*/
run;
/*2 身高数据整理*/
/*2.1 获取描述数据*/
ods output summary=desc2;
proc means data=sashelp.heart mean std;
class sex;
var height;
run;
data desc2(keep=sex meanstd);
set desc2;
meanstd = cats(put(height_mean,12.1), "±", put(height_stddev,12.1));
run;
/*2.2 转置,性别作为列*/
proc transpose data=desc2 out=desc2 prefix=col;
var meanstd;
id sex;
run;
/*2.3 判断方差齐性,获取t检验p值*/
ods output equality=eql ttests=ttl;
proc ttest data=sashelp.heart plots=non;
class sex;
var height;
run;
data pvalue2(keep=Probt rename=(Probt=Pvalue));
retain equV;
set eql ttl;
if ProbF<0.05 then equV=0;
else equV=1;
if (equV=1 and variances="Equal") or (equV=0 and variances="Unequal");
run;
/*2.4 合并变量标签,描述数据,p值*/
data merge2(keep=label col: pvalue);
length label $85;
merge desc2 pvalue2;
label=cats("^S={font_weight=bold}", "Height (in)"); /*RTF代码,加粗标签*/
run;
/*3 合并数据*/
data dsreport;
set merge:;
run;
/*4 Report输出*/
options nodate nonumber missing=' ';
ods tagsets.rtf file="table6.rtf" style=journal3a;
ods escapecha='^';
title "Table 1.Blood Presure and Height by Sex";
proc report data=dsreport nowd;
column label colfemale colmale pvalue;
define label / display "Variables";
define colfemale / display "Female" right;
define colmale / display "Male" right;
define pvalue / analysis "P Value" f=pvalue6.4;
run;
footnote1 j=center height=10pt "^{nbspace}Note: This is a demo";
ods tagsets.rtf close;

一缕檀烟万佛名:宏中奥秘

###宏

SAS宏由宏语言和宏处理器构成。宏语言是与宏处理器沟通的语言。宏语言中以&开头的是宏变量,以%开头的是SAS宏程序或宏函数。当SAS遇到&或%开头的宏语言时,便触发宏处理器,执行宏操作。

创建宏变量

  • %LET语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*文本*/
%let bookname1=Romance of SAS Program;
%let bookname2=SAS编程演义;
%let address1=%str(Author%'s address);
%let address2=%nrstr(ShanghaiTech&SMMU);
/*数字*/
%let phone1=13800137000;
%let phone2=;
/*算术表达式*/
%let ex1=99+1;
%let ex2=99.9+0.1;
/*计算算术表达式*/
%let ex3=%eval(99+1);
%let ex4=%sysevalf(99.9+0.1);
/*程序*/
%let prg=%str(proc print data=sashelp.classr;run;);
/*查看结果*/
%put &bookname1 &bookname2 &address1 &address2 &phone1 &phone2 &ex1 &ex2 &ex3 &ex4 &prg;
  • %GLOBAL申明一个全局宏变量(无法赋值),既可以在宏程序内部,也可以在外部。
  • %LOCAL申明一个局部宏变量(无法赋值),只能在宏程序内部。
    • 确保宏程序内部定义的宏变量不会因为同名重写全局宏变量的值
    • 确保宏程序执行完毕后,这些宏变量立即消逝
  • SQL里SELECT语句的INTO从句,可产生一个或一系列宏变量
1
2
3
4
5
6
7
8
9
10
11
/*单个宏变量*/
proc sql noprint;
select count(name) into:nname from sashelp.class;
quit;
%put &nname;
/*宏变量列表*/
proc sql noprint;
select name into:namelist separated by "," from sashelp.class;
quit;
%put &namelist;
  • DATA步的SYMPUT和SYMPUTX(去除首尾空格)语句。
1
2
3
4
5
6
7
8
9
10
11
12
13
/*单个宏变量*/
data _null_;
set sashelp.class end=last;
if last then call symput('nname',_n_);
run;
%put &nname;
/*宏变量列表*/
data _nuul_;
set sashelp.calss;
call symputx(cats('name',_n_),name);
run;
%put &namelist;
  • 宏程序的宏参数会创建局部宏变量
  • %WINDOWS语句
  • 宏程序中的%DO语句
  • ODS OUTPUT语句的MATCH_ALL选项

宏符号表与作用域

宏符号表(Macro Symbol Table)存储着宏变量与其对应的值。

  • 全局宏符号表,SAS启动时,系统自动创建,其中存储的内容有:
    • 系统自带的宏变量和值
    • open code(宏程序外)环境下,用%LET语句创建的宏变量
    • 宏程序定义中,用%GLOBAL申明的宏变量
    • DATA步中CALL SYMPUTX语句中申明了全局符号表
    • PROC SQL中SELECT语句的INTO从句创建的宏变量
  • 局部宏符号表,SAS执行宏程序时,系统自动创建,其中存储的内容有:
    • 由宏程序的宏参数创建的宏变量
    • 宏程序中由%LET定义的宏变量,且在全局宏变量中没有同名宏变量
    • 宏程序中由%LOCAL定义的宏变量
  • 判断是全局还是局部
    • 采用%PUT语句的选项_USER_、_LOCAL_、_GLOBAL_查看符号表
    • 采用系统自带的宏函数%SYMLOCAL、%SYMGLOBAL进行判定
1
2
3
4
5
6
7
8
9
10
11
12
%macro testMacroVar(a=, b=);
%put 所有自定义宏变量;
%put _user_;
%put 所有局部宏变量;
%put _local_;
%put 所有全局宏变量;
%put _global_;
%put sysver是否全局宏变量: %symglobl(sysver);
%put a是否全局宏变量: %symglobl(a);
%put b是否局部宏变量: %symlocal(b);
%mend;
%testMacroVar(a=Stats, b=Thinking)

掩蔽宏变量

掩蔽(masking):将符号当作普通的文本对待,不使用其特殊含义,有的文档也称为引用(quoting)。

  • 需要掩蔽的符号:
    • 运算符:如+ - * / < > = ¬ ^ ~ |
    • 助记符:如 AND OR NOT EQ NE LE LT GE GT IN
    • 其他:bank , ; “ “ ‘ ’ ( ) #
  • 没有配对的符号,如 “ ‘ ( ),使用%B系列的函数掩蔽
  • 宏触发器 & %,使用%NR系列的引用函数
  • 移除掩蔽主要发生在程序编译后、程序运行后、%UNQUOTE函数后

显示宏变量值

  • 系统选项SYMBOLGEN
  • %PUT语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
options symbolgen;
%let title1=SAS编程演义;
%let title2=数据整理与图表呈现;
%let title=&title1.:&title2;
options nosymbolgen;
%let title1=SAS编程演义;
%let title2=数据整理与图表呈现;
%let title=&title1.:&title2;
%put title1和title2合并后的title解析为:&title;
%put _all_; /*显示所有宏变量*/
%put _automatic_; /*显示所有自动宏变量*/
%put _user_; /*显示所有自定义宏变量*/
%put _global_; /*显示所有全局宏变量*/
%put _local_; /*显示所有局部宏变量*/

引用宏变量

  • 直接引用

如果是用宏变量给DATA步变量赋具体的值,需用引号引起来。

1
2
3
4
5
6
%let bookname2017=SAS编程演义;
%let year=2017;
data tmp;
name="&bookname2017";
year=&year;
run;
  • 间接引用

SAS在解析时,会将两个连续的&&解析为一个&,然后再次进行解析,知道&全被解析完

1
2
3
4
5
6
7
8
9
10
11
%let bookname2017=SAS编程演义;
%let year=2017;
data tmp;
name="&bookname&&year";
run;
%let L1=L2;
%let L2=L3;
%put &L1; /*L2*/
%put &&L1; /*&L1 -> L2*/
%put &&&L1; /*&L2 -> L3*/
  • 合并宏变量与普通文本

宏变量后面带一个点号,用以标识宏变量的结束

1
2
3
4
5
6
%let bookname2017=SAS编程演义;
%let lib=sashelp;
data class;
set &lib..class;
bookname="&bookname2017.:数据整理与图表呈现";
run;

宏程序定义与调用

%macro macro_name <parameter_list> </option(s)\>

​ <macro_text>

%mend <macro_name>;

%macro_name <parameter_list>

  • macro_name:宏程序名称,需要符合SAS命名规范,不能以SYS、AF、DM开头,不能用SAS的保留字,以免冲突
  • parameter_list:宏参数,通过宏程序定义的局部变量,便于在宏文本中应用。参数通过逗号隔开
    • 位置宏参数:按位置顺序定义识别 <positional-parameter-1><, positional-parameter-2…>
    • 关键字宏参数:按名字=识别 <keyword-parameter=<value> <, keyword-parameter-2=<value> …>>
  • option(s):比如加密选项secure,存储选项store
  • macro_text:宏文本是宏的主体,里面包括宏语句、DATA步语句、PROC语句等
  • 宏参数不是必需的,位置宏参数和关键字宏参数可以混用,但位置宏参数必须在关键字宏参数前。
  • 宏参数中如有特殊字符如&、%、不配对的括号或引号、其他运算符等,需要做好宏变量的掩蔽。
  • %mend之后的宏程序名非必需。
  • 调用宏程序时,末尾不需要分号结尾。

宏程序的存储加密

如果为了保护知识产权,想给他人使用宏程序但又不希望他人看到自己开发的宏程序源代码,可以将编译好的宏给别人,且设置为不显示宏的源代码。

1
2
3
4
5
6
7
8
9
options mstored sasmstore=demo /*指定存储位置为demo永久库*/
%macro printds(dataset, obs=5) / store;
options nomprint nosource; /*不显示源代码*/
proc print data=&dataset(obs=&obs);
run;
%mend printds;
options mstored sasmstore=demo;
%printds(sashelp.class, obs=5)

%IF-%THEN-%ELSE选择语句

1
2
3
4
5
6
7
8
%macro printds(dataset, sex=F, obs=5,);
%if &sex eq F %then %str(title "First &obs record for female";);
%else %if &sex eq M %then %str(title "First &obs record for male";);
%else %str(title "Wrong gender"; %abort;);
proc print data=&dataset(obs=&obs where=(sex="&sex"));
run;
%medn printde;
%printde(sashelp.class,sex=M,obs=5)

%DO组语句

1
2
3
%do
语句;
%end

%DO循环语句

1
2
3
4
5
6
7
8
%macro importcsv;
%do i=1 %to 100;
proc import out=csv&i datafile="d:\data\casv&i.csv" dbms=csv replace;
run;
%end;
%mend;
%importcsv

%DO-%WHILE语句

1
2
3
4
5
6
7
8
9
10
%macro importcsv;
%let i=1;
%do %while(&i<=100);
proc import out=csv&i datafile="d:\data\casv&i.csv" dbms=csv replace;
run;
%let i=&i+1;
%end;
%mend;
%importcsv

%DO-%UNTIL语句

1
2
3
4
5
6
7
8
9
10
%macro importcsv;
%let i=1;
%do %until(&i>100);
proc import out=csv&i datafile="d:\data\casv&i.csv" dbms=csv replace;
run;
%let i=&i+1;
%end;
%mend;
%importcsv

宏函数

类别 宏函数 作用
字符宏函数 %index 返回字符串第一次出现的位置
%length 返回字符串长度
%scan %qscan 按字符串中的位置截取单词
%substr %qsubstr 截取子字符串
%upcase %qupcase 转为大写字符
计算函数 %eval 使用整数运算计算表达式结果
%sysevalf 使用浮点运算计算表达式结果
引用函数 %str 编译时掩蔽特殊字符及助记符
%nrstr %str 外加宏触发器字符
%quote 执行时掩蔽特殊、助记、其他字符
%nrquote %quote 外加宏触发器字符
%bquote 执行时掩蔽特殊、助记、其他字符和没有配对的符号
%nrbquote %bquote 外加宏触发器字符
%superq 执行时掩蔽所有字符,且对结果保持
%unquote 执行时去掉所有特殊、助记字符的掩蔽
SYS系函数 %sysmacexect 返回宏执行状态
%sysmacexist 判断宏程序是否已经定义
%sysmexecdepth 返回宏嵌套的深度
%sysmexecname 返回指定宏嵌套的深度的宏名称
%sysfunc %qsysfunc 使用data步函数
%sysget 返回环境变量的值
%sysprod 判断SAS产品是否已经授权
SYM系函数 %symexist 判断宏变量是否已经存在
%symglobl 判断是否属于全局宏变量
%symlocal 判断是否属于局部宏变量

宏开发

  1. 硬代码实现:先不用任何宏代码,实现具体的任务
  2. 移除上一步中的硬代码,该用宏变量、宏参数,如有必要,增加宏语句如以及宏函数
  3. 宏代码测试优化:给宏变量赋值,逐步测试宏代码,优化宏代码。