流行病学研究中基线表格的SAS宏,本文主要介绍分组变量为多组的SAS宏实现基线表格,后期会以视频的形式讲解宏的具体使用方法。
通过阅读本文,可以了解本套宏大概采取了哪些统计学方法。什么情况对应什么方法?
随着计算机的快速发展,医学科研工作者可借助专业统计软件快速实现数据的统计指标,但传统的复制粘贴不仅效率低下且易出现错误[1]。SAS软件作为科研人员公认的统计分析软件,可利用其宏功能实现各类统计结果word报表的自动化输出。本文编写了两套流行病学研究中基线表格的SAS宏程序,为提高科研人员工作效率提供借鉴。
基本原理与统计方法
流行病学研究包括观察性研究和实验性研究,其中对于非配对设计的横断面研究、队列研究和实验性研究,研究者可按照某暴露因素或不同干预措施将研究对象分为两组和多组[2],在统计分析时往往这一变量称之为分组变量,其他变量为分析变量。当分组变量为两组时,定量资料通常采用均数±标准差或中位数(四分位数间距)进行统计描述,组间比较包括t检验、Satterthwaite方法校正的t'检验和Wilcoxon秩和检验;定性资料采用例数(构成比)进行统计描述,组间比较包括c2检验、连续校正c2检验和Fisher精确概率法[3, 4]。当分组变量为三组及以上时,定量资料通常采用均数±标准差或中位数(四分位数间距)进行统计描述,组间比较包括方差分析、Welch 近似方差分析[5]和Kruskal-Wallis秩和检验;定性资料采用例数(构成比)进行统计描述,组间比较包括c2检验、连续校正c2检验和Fisher精确概率法[3, 4]。不同统计指标和统计方法选择条件详见表1。
表1流行病学研究中基线表格的SAS宏统计概述
案例分析和SAS宏实现
本文以2009年中国健康与营养调查数据为例,将研究对象按照其自报家务体力活动水平的四分位数分为Q1、Q2、Q3、Q4四组,对其基线特征进行描述,即分组变量为家务体力活动水平,预分析的基线特征变量包括分类变量(省份和性别)和连续性变量(年龄和脂肪供能比)。拟生成的word表格见表2。这里介绍本文宏程序的使用规则,具体的宏程序见附录。
1.设置逻辑库和定义数据格式 首先需要定义数据所在逻辑库,“cert”为原逻辑库,“result”为处理逻辑库,这样可以避免数据处理过程中对原数据集出现误删或更改。首先所有的变量必须为数值型变量,其中对于分类变量需要利用proc format过程步定义变量输出格式,对于分析变量,-99定义为此变量的项目名称,比如-99=’性别’。上述定义变量格式的优势在于对于分类变量的分类更加灵活,只需利用此步骤即可实现分类,尤其是对于诸如年龄等连续变量转化为分类变量时无需生成新变量,操作简单方便且效率较高。
libname cert 'C:\Users\12974\Desktop\sas程序\自我开发宏\三组基线表格宏开发\cert';
libname result 'C:\Users\12974\Desktop\sas程序\自我开发宏\三组基线表格宏开发\result';
option validvarname=any;/*开启中文命名*/
/*定义变量格式*/
/*分组变量:家务体力活动水平*/
proc format;
value jiawu low-16.74='Q1' 16.74<-33.08='Q2' 33.08<-51.80='Q3' 51.80<-high='Q4';/*供参考*/
quit;
/*分析变量:-99=后面写分析变量的条目*/
proc format;
value province -99='省份' 11='北京' 21='辽宁' 23='黑龙江' 31='上海' 32='江苏' 37='山东' 41='河南' 42='河北' 43='湖南' 45='广西' 52='贵州' 55='重庆';
value sex -99='性别' 1='男' 2='女';
quit;
2.数据预处理 第二步进行数据预处理,这里数据必须已经完成清洗和整理,只需将利用format语句定义输出格式,比如 format 性别 sex.,代码如下:
/*数据预处理:1.要求所有变量为数值型
2.要求输入分组变量和分析变量的格式*/
data result.ms;
set cert.ms;
format 家务体力活动水平 jiawu.
省份 province. 性别 sex.;
run;
3.导入宏和定义宏变量 本文开发的针对分组变量为三组以下的基线宏文件命名为“baseline_3”,其中包含“%baseline3_feilei”和“%baseline3_
lianxu”两个宏程序。其中%baseline3_feilei(x=,ylist=,dataset=)是针对分析变量为分类变量而开发的,宏变量&x.代表分组变量,&ylist.代表分析变量列表,以空格隔开,&dataset.代表分析数据集;%baseline3_lianxu(x=,
ylist=,dataset=,normal=)是针对分析变量为连续变量而开发的,宏变量&x.代表分组变量,&ylist.代表分析变量列表,以空格隔开,&dataset.代表分析数据集,&normal指定分析变量是否符合正态分布,1代表正态,2代表非正态;若存在既存在正态分布又包括非正态的分布的变量,可以调用两次
%baseline3_lianxu宏,一组为正态变量,另一组为非正态变量。具体导入和定义宏代码如下:
/*导入宏*/
%include "C:\Users\12974\Desktop\sas程序\自我开发宏\三组基线表格宏开发\SAS程序\baseline_3.sas";
/*调用分类变量宏:
x:分组变量;ylist:分析变量列表,以空格隔开;dataset:为分析数据集*/
%baseline3_feilei(x=家务体力活动水平,ylist=省份 性别,dataset=ms);
/*调用连续变量宏
x:分组变量;ylist:分析变量列表,以空格隔开;dataset:为分析数据集;
normal:正态性判断(1代表正态,0代表非正态)*/
%baseline3_lianxu(x=家务体力活动水平,ylist=年龄 脂肪供能比,dataset=ms
,normal=1);
4.合并数据集并输出报表 在上述第三步运行之后,SAS的临时数据库会生成“feilei”(包含所有分析变量为分类变量的结果)和“lianxu” (包含所有分析变量为连续变量的结果)两个数据集。由于SAS对于P<0.0001的结果默认输出为“<0.0001”,本研究的宏代码会将“<0.0001”的结果输出为“0”,因此这里首先通过proc format过程将“0”定义输出为“<0.0001”。最后合并两个数据集为“gg”并通过ods结合proc report过程进行输出word报表,此处研究者只需更改“ods rtf file=”之后的文件路径即可,继续运行以下代码即可得到表2所示的基线表格,具体代码如下:
/*合并分类变量数据集feilei和连续变量数据集lianxu*/
proc format;
value P 0='<0.0001';
quit;
data gg;
set feilei lianxu;
format P P.;
run;
/*输出数据集*/
ods escapechar="^";
option nodate nonumber;
ods rtf file="C:\Users\12974\Desktop\sas程序\自我开发宏\三组基线表格宏开发\report.rtf" style=journal3a bodytitle;
proc report data=gg
style(header)=[just=c font_size=9pt fontfamily="宋体" font_weight=medium]
style(column)=[just=c font_size=9pt fontfamily='宋体' cellwidth=2.2 cm];
define label/display "变量" style(column)=[just=l];
quit;
ods rtf close;
表2流行病学研究三组及以上基线特征分布
变量 |
Q1 |
Q2 |
Q3 |
Q4 |
统计量 |
P |
方法 |
省份 |
310.01 |
<0.0001 |
pearson卡方检验 |
||||
辽宁 |
78(6.93) |
110(9.62) |
143(12.65) |
201(17.93) |
|||
黑龙江 |
110(9.77) |
75(6.56) |
133(11.77) |
196(17.48) |
|||
江苏 |
152(13.5) |
146(12.76) |
115(10.18) |
151(13.47) |
|||
山东 |
122(10.83) |
135(11.8) |
111(9.82) |
72(6.42) |
|||
河南 |
117(10.39) |
109(9.53) |
123(10.88) |
121(10.79) |
|||
河北 |
104(9.24) |
119(10.4) |
136(12.04) |
83(7.4) |
|||
湖南 |
104(9.24) |
121(10.58) |
147(13.01) |
157(14.01) |
|||
广西 |
252(22.38) |
246(21.5) |
150(13.27) |
67(5.98) |
|||
贵州 |
87(7.73) |
83(7.26) |
72(6.37) |
73(6.51) |
|||
性别 |
0.0001 |
Fisher精确概率法 |
|||||
男 |
6(0.53) |
3(0.26) |
3(0.27) |
1(0.09) |
|||
女 |
240(21.31) |
653(57.08) |
885(78.32) |
993(88.58) |
|||
年龄 |
46.27±14.82 |
46.93±13.81 |
48.17±12.74 |
48.72±12.01 |
7.9 |
<0.0001 |
Welch近似方差分析 |
脂肪供能比 |
30.77±10.62 |
32.01±10.18 |
31.81±10.61 |
32.59±10.48 |
5.85 |
0.0006 |
方差分析 |
注:对于存在缺失变量,本SAS宏仅保留非缺失数据,检验结果是基于非缺失数据产生的。
讨论
本文利用SAS软件分别开发了“baseline_2”(应用于分组变量为二分类)和“baseline_3”(应用于分组变量为多分类)两套宏,并简要介绍了“baseline_3”的使用方法和流程,两套宏在使用上并无差别,仅仅只是统计分析方法存在差异。与以往相关的宏相比,本文开发的宏在内容上更加全面,几乎可以满足流行病学研究中横断面、队列、实验性研究的基线样本特征描述的需求,并且给出了分组比较的方法,便于研究者进行参考。
参考文献
[1] 徐凯, 孙瑞华, 李欢, 等. 临床试验中定性指标统计分析报表的SAS宏实现[J]. 中国卫生统计, 2017,34(05): 836-838.
[2] 李立明, 詹思延, 叶冬青, 等. 流行病学第8版[M]. 8. 北京: 人民卫生出版社, 2017.
[3] 方积乾, 徐勇勇, 陈峰, 等. 卫生统计学第7版[M]. 7. 北京: 人民卫生出版社, 2012.
[4] 冯国双, 罗凤基, 周春莲, 等. 医学案例统计分析与SAS应用[M]. 2. 北京大学医学出版社, 2015.
[5] 王雨萌, 孙瑞华, 黄傲, 等. 单因素多水平临床试验定量指标统计分析报表的SAS宏实现[J]. 中国卫生统计, 2016,33(04): 712-714.
分组变量为3组的SAS宏附录:
/*三组分类变量比较:卡方检验或fisher精确概率法*/
%macro baseline3_feilei(x=,ylist=,dataset=);
%let i=1;
%do %while(%scan(&ylist. , &i. , %str( ) ) ne %str() );
%let y =%scan(&ylist. , &i. , %str( ) );
/*表1:人口学信息*/
proc tabulate data=&dataset. out=table;
class &y. &x.;
table (&y.),(&x.)*(n colpctn)/nocellmerge;
quit;
/*合并频数(构成比)*/
data table;
set table;
n_pctn=compress(n||'('||round(PctN_01,0.01)||')',' ');
run;
/*转置*/
proc sort data=table;
by &y.;
quit;
proc transpose data=table out=table(drop=_name_);
var n_pctn;
by &y.;
id &x.;
quit;
/*重命名分析变量省份为“变量”*/
data table;
set table(rename=(&y.=变量));
label 变量=' ';
run;
/*插入一个空行*/
proc sql;
insert into table set 变量=-99;
quit;
proc sort data=table;
by 变量;
quit;
/*R*C列联表计算总频数和理论频数形成条件数据集*/
ods output Crosstabfreqs=Crosstabfreqs;
proc freq data=&dataset.;
table &x.*&y./expected nocol norow nopercent;
quit;
/**选取条件数据集**/
data a(keep=Frequency) b(keep=Expected);
set Crosstabfreqs end=last;
if last then output a;
if &x. ne . and &y. ne . then output b;
run;
/*总样本量、总格子数,理论频数小于5的格子数,理论频数小于1的格子数*/
data b;
set b;
n=_N_;/*总格子数*/
if expected<5 then expected5=1;else expected5=0;
if expected<1 then expected1=1;else expected1=0;
run;
/*计算*/
proc sql;
create table c as
select n,sum(expected5) as expected5,sum(expected1) as expected1
from b;
quit;
/*选取最后一条*/
data c;
set c end=last;
if last;
run;
/*合并*/
data tiaojian;
merge a c;
rename n=总格子数;
run;
/*删除生成的临时数据集*/
proc datasets lib=work nodetails;
delete a b c Crosstabfreqs;
quit;
/*把条件数据集的"总格子数,expected5,expected1"做成宏变量*/
proc sql noprint;
select 总格子数,expected5,expected1
into :总格子数,:expected5,:expected1
from tiaojian;
quit;
%put &总格子数.;%put &expected5.;%put &expected1.;
%macro p;
/*获取普通卡方值的条件:1.理论<5的格子数不超过20%;2.理论<1的格子数为0*/
%if &expected5. le %sysevalf(&总格子数. * 0.2) and &expected1.=0 %then %do;
ods output chisq=chisq;
proc freq data=&dataset.;
table &x.*&y./chisq noprint;
quit;
/*整理*/
data p;
retain 统计量 P 方法;
length 方法 $50.;
set chisq(keep=value prob rename=(value=统计量 prob=P));
统计量=round(统计量,.01);
P=round(P,.0001);
if _n_=1;
label 统计量=' ' P=' ';
方法='pearson卡方检验';
format 统计量 best12.2 P best12.4;
run;
%end;
/*fisher P:1.理论<5的格子数大于20%;或2.理论<1的格子数大于0*/
%else %if &expected5. gt %sysevalf(&总格子数. * 0.2) or &expected1. gt 0 %then %do;
/*输出fisher P*/
ods output FishersExactMC=fisher;
proc freq data=&dataset.;
table &x.*&y./noprint;
exact fisher/MC;
quit;
/*整理*/
data p;
retain 统计量 P 方法;
length 方法 $50.;
set fisher(keep=cValue1);
cValue1=compress(cValue1,'<');
统计量=.;
P=input(cValue1,best12.);
if _N_=1;
drop cValue1;
方法='Fisher精确概率法';
format 统计量 best12.2 P best12.4;
run;
%end;
%mend;
%p;
/*描述统计和P合并*/
data zong&i.;
merge table p;
run;
/*删除相关数据集*/
proc datasets lib=work nodetails;
delete chisq fisher p table tiaojian;
quit;
/*输出格式*/
ods output position=a1;
ods select position;
proc contents data=&dataset. varnum ;
quit;
/*选取*/
proc sql;
select format
into :geshi
from a1
where variable="&y.";
quit;
%put &geshi.;
/*整理:将分析变量格式应用*/
data zong&i.(drop=变量);
length label $50.;
set zong&i.;
label=put(变量,&geshi.);
if _n_=1 then label="^S={font_weight=bold}"||left(label);/*加粗*/
if _n_>1 then label="^R'\li250'"||left(label);/*缩进250*/
run;
%let i=%eval(&i.+1);
%end;
/*合并*/
data feilei;
set zong:;
run;
/*删除相关数据集*/
proc datasets lib=work nodetails;
delete a1 zong:;
quit;
%mend;
/*三组连续变量比较:方差分析和KW-H检验*/
%macro baseline3_lianxu(x=,ylist=,dataset=,normal=);
%let i=1;
%do %while(%scan(&ylist. , &i. , %str( ) ) ne %str() );
%let y =%scan(&ylist. , &i. , %str( ) );
/*方差齐性检验*/
ods output HOVFTest=hovtest;
ods select HOVFTest;
proc glm data=&dataset.;
class &x.;
model &y.=&x.;
means &x./hovtest;
quit;
/*整理*/
data hovtest;
set hovtest;
if ProbF<0.05 then p=0;
else p=probf;
run;
/*将方差齐性检验p值赋予宏变量*/
proc sql inobs=1;
select p
into :fangcha_P
from hovtest;
quit;
%put &fangcha_p.;
/*根据正态性和方差齐性选择描述和检验方法*/
%macro P2;
/*正态并且方差齐性:描述:均数±标准差;组间检验:方差分析*/
%if &normal.=1 and &fangcha_p.>=0.05 %then %do;
proc tabulate data=&dataset. missing out=table;
class &x.;
var &y.;
table &x.,&y.*(mean std)/nocellmerge;
quit;
/*整理*/
data table;
set table;
&y.=compress(round(&y._mean,.01)||'±'||round(&y._std,.01),' ');
run;
/*转置*/
proc transpose data=table out=table(rename=(_name_=label));
var &y.;
id &x.;
quit;
/*项目“label”宽度设为50,去掉标签*/
data table;
length label $50;
set table;
label label=' ';
run;
/*方差分析计算统计量F值和P值*/
ods output ModelANOVA=P;
ods select ModelANOVA;
proc glm data=&dataset.;
class &x.;
model &y.=&x.;
quit;
/*整理P*/
data p;
retain 统计量 P 方法;
length 方法 $50.;
set p(keep=FValue probf rename=(fvalue=统计量 probf=P));
if _n_=1 then delete;
统计量=round(统计量,.01);
P=round(P,.0001);
label 统计量=' ' P=' ';
方法='方差分析';
format 统计量 best12.2 P best12.4;
run;
%end;
/*正态并但方差不齐:描述:均数±标准差;组间检验:Welch近似方差分析*/
%else %if &normal.=1 and &fangcha_p.<0.05 %then %do;
proc tabulate data=&dataset. missing out=table;
class &x.;
var &y.;
table &x.,&y.*(mean std)/nocellmerge;
quit;
/*整理*/
data table;
set table;
&y.=compress(round(&y._mean,.01)||'±'||round(&y._std,.01),' ');
run;
/*转置*/
proc transpose data=table out=table(rename=(_name_=label));
var &y.;
id &x.;
quit;
/*项目“label”宽度设为50,去掉标签*/
data table;
length label $50;
set table;
label label=' ';
run;
/*Welch近似方差分析计算统计量F值和P值*/
ods output Welch=P;
ods select Welch;
proc anova data=&dataset.;
class &x.;
model &y.=&x.;
means &x./welch;;
quit;
/*整理P*/
data p;
retain 统计量 P 方法;
length 方法 $50.;
set p(keep=FValue probf rename=(fvalue=统计量 probf=P));
if _n_=2 then delete;
统计量=round(统计量,.01);
P=round(P,.0001);
label 统计量=' ' P=' ';
方法='Welch近似方差分析';
format 统计量 best12.2 P best12.4;
run;
%end;
/*非正态:描述:中位数(四分位数间距);组间检验:KW-H检验*/
%else %if &normal.=0 %then %do;
proc tabulate data=&dataset. missing out=table;
class &x.;
var &y.;
table &x.,&y.*(median Qrange)/nocellmerge;
quit;
/*整理*/
data table;
set table;
&y.=compress(round(&y._median,.01)||'('||round(&y._Qrange,.01)||')',' ');
run;
/*转置*/
proc transpose data=table out=table(rename=(_name_=label));
var &y.;
id &x.;
quit;
/*项目“label”宽度设为50,去掉标签*/
data table;
length label $50;
set table;
label label=' ';
run;
/*KW-H检验计算统计量和P值*/
ods output KruskalWallisTest=P;
ods select KruskalWallisTest;
proc npar1way data=&dataset. wilcoxon;
class &x.;
var &y.;
quit;
/*整理P*/
data p;
set p;
if _n_=2 then delete;
nValue1=round(nValue1,.01);
run;
data p;
retain 统计量 P 方法;
length 方法 $50.;
array x 统计量 P;
do over x;
set p(keep=nValue1);
x=nvalue1;
end;
方法='Kruskal-Wallis检验';
drop nValue1;
format 统计量 best12.2 P best12.4;
run;
%end;
%mend;
%p2;
/******合并描述和P为总******/
data zong&i.;
merge table P;
label="^S={font_weight=bold}"||left(label);/*加粗*/
run;
%let i=%eval(&i.+1);
%end;
/*合并lianxu*/
data lianxu;
set zong:;
run;
proc datasets lib=work nodetails;
delete hovtest table p zong:;
quit;
%mend;