在临床试验TFL编程中,简单的描述性统计量与频数汇总表格的数量占表格总量的绝对大头。从提高编程效率的角度看,为这两类表格建立稳定的宏程序输出是一件非常高效率的事情。
更多临床试验SAS编程内容,欢迎关注:SAS茶谈。
这篇文章介绍,分类变量简单频数汇总宏程序的处理。主要有5方面的内容:
- 试验汇总组的处理
- BigN的生成
- 固定分类的Format设置
- 频数百分比的计算
- 横向数据转换为纵向数据
输出Table的样式,各家基本相同,这篇文章采用以下样式:
计算统计量的过程步选择Proc Means, 由于Means过程步只针对数值型变量进行分析,还需要新建一个数值变量用于计数(flag = 1
)。
分析数据来源于SASHELP.CLASS数据集,简单处理下,新建一个分组变量:
data class0;
set sashelp.class (in = a)
sashelp.class (in = b);
if a then trt01pn = 1;
if b then trt01pn = 2;
flag = 1;
run;
1. 试验汇总组的处理
TFLShell中会明说明输出Table是否有汇总组。最常见的处理汇总组的方式是,在原始数据集利用Data步的Output
语句,新建汇总组;我推荐大家尝试使用Format过程步中的Multilabel
选项进行构建汇总组,这个方法不需要在原始数据集中进行新建分组处理,一定程度减少了分析数据集的观测数。
具体介绍文章参考:SAS编程:生成Table时,汇总组(Total)组如何处理?。
示例代码如下:
proc format;
value trt01pn (notsorted multilabel)
1 = 1
2 = 2
1,2 = 99
;
run;
2. BigN的生成
由于频数百分比的计算基于BigN,所以这里先计算输出BigN,并将BigN的值保存到宏变量中。使用Means过程步中Class语句的preloadfmt
、mlf
选项,可以输出分类格式的结果,包括提前定义好的汇总组。
** Derive BigN and save them to macro vars;
proc means data = class0 nway completetypes;
format trt01pn trt01pn.;
class trt01pn / preloadfmt mlf order = data;
var flag;
output n = bign out = BigN;
run;
data _null_;
set BigN;
call symputx("N_"||strip(trt01pn), strip(put(bign, best.)));
run;
输出结果如下:
BigN输出到数据集中,方便之后与计数结果数据集进行拼接,计算百分比。
3. 固定分类的Format设置
在考虑固定分类Format设置之前,先确认下首行文字信息如何处理。之前文章提到过,常用的2种方法是,新建一个数据集与结果数据集纵向拼接,或是在结果数据集种多Output一行记录。
我这里提供另一种方法,利用Format过程步中Mlultilabel
选项,新建汇总组,汇总组的Format值保留文字说明信息。如果首行只需要文字说明信息,不需要频数百分比,最后在结果数据集中删除首行中的频数百分比的内容。
考虑到固定顺序,为每一个Format值建立一个Informat的排序数值。
proc format;
value $sex (notsorted multilabel)
"M", "F" = "Sex - n (%)"
"M" = "Male"
"F" = "Female"
;
invalue sexn
"Sex - n (%)" = 0
"Male" = 1
"Female" = 2
;
run;
4. 频数百分比的计算
应用Means过程步进行计算分类频数,考虑到试验分组变量与分析分组变量都需要输出所有可能类别的结果,对这2个变量都需要使用Class语句中的preloadfmt
选项。
如果只需要输出数据集中已有的分类的结果,不需要输出未出现的可能结果,可以直接将分组变量设为by
语句变量。
**Get small n;
proc means data = class0 nway completetypes;
format trt01pn trt01pn.;
class trt01pn / preloadfmt mlf order = data;
format sex $sex.;
class sex/ preloadfmt mlf order = data;
var flag;
output n = count out = count1;
run;
输出结果如下:
这里可以直接与BigN数据集进行拼接,计算出百分比。
**Get percentage;
data count2;
merge count1 bign(keep = trt01pn bign);
by trt01pn;
section = 1;
cat1n = input(sex, sexn.);
length col1 $200;
col1 = sex;
length freq $200;
if bign ne 0 then freq = strip(put(count, best.))||" ("||strip(put(count/bign*100, 8.1))||")";
else freq = "0 (-)";
proc sort;
by section cat1n col1 trt01pn;
run;
输出结果如下:
这里百分比直接给了8.1
的Format,如果公司或统计师有其他要求,可以直接定义好Format进行引用,例如以下两种格式:
proc format;
value freq
0-<0.1 = "<0.1"
0.1-<99.95 = [4.1]
99.95-100 = "100"
;
prcture freq
0-<0.1 = "<0.1" (noedit)
0.1-<9.95 = "009.9)" (prefix="( ")
9.95-<99.95 = "009.9)" (prefix="( ")
99.95-100 = "(100) " (noedit)
;
run;
程序中只需更新下Format名称就好:
if bign ne 0 then freq = strip(put(count, best.))||" ("||strip(put(count/bign*100, freq.))||")";
5. 横向数据转换为纵向数据
考虑代码与输出结果的简洁性,选用Transpose过程步进行转置,不再使用Data步中Output
语句。
**Transpose results;
proc transpose data = count2 out = count3 prefix = trt_;
by section cat1n col1;
var freq;
id trt01pn;
run;
输出结果如下:
考虑到不需要第一行的文字说明信息,可以直接将信息置空:
**Create output dataset;
data count4;
set count3;
if cat1n = 0 then call missing(of trt_:);
drop _name_;
run;
最后的输出结果与Shell的中的内容相同。
6. 完整宏程序汇总
这个宏程序的参数出了输入的变量外,还有分类变量对应的Format,首行信息的内容也是通过Format来控制。读者也可以根据自己的需要进行宏的更新。
%macro catn(indt=, trtvar=, trtfmt=, catvar=, catfmt=, catnfmt=, section=, outdt=);
**Get small n;
proc means data = &indt. nway completetypes;
format &trtvar. &trtfmt..;
class &trtvar./ preloadfmt mlf order = data;
format &catvar. $&catfmt..;
class &catvar./ preloadfmt mlf order = data;
var flag;
output n = count out = count1;
run;
**Get percentage;
data count2;
merge count1 bign(keep = &trtvar. bign);
by &trtvar.;
section = §ion.;
cat1n = input(&catvar., &catnfmt..);
length col1 $200;
col1 = &catvar.;
length freq $200;
if bign ne 0 then freq = strip(put(count, best.))||" ("||strip(put(count/bign*100, 8.1))||")";
else freq = "0 (-)";
proc sort;
by section cat1n col1 &trtvar.;
run;
**Transpose results;
proc transpose data = count2 out = count3 prefix = trt_;
by section cat1n col1;
var freq;
id &trtvar.;
run;
**Create output dataset;
data &outdt.;
set count3;
if cat1n = 0 then call missing(of trt_:);
drop _name_;
run;
%mend catn;
proc format;
value trt01pn (notsorted multilabel)
1 = 1
2 = 2
1,2 = 99
;
value $sex (notsorted multilabel)
"M", "F" = "Sex - n (%)"
"M" = "Male"
"F" = "Female"
;
invalue sexn
"Sex - n (%)" = 0
"Male" = 1
"Female" = 2
;
run;
**Invoke the macro;
%catn(
indt = class0
,trtvar = trt01pn
,trtfmt = trt01pn
,catvar = sex
,catfmt = sex
,catnfmt = sexn
,section = 1
,outdt = sec_1
);
以上程序运行,与之前结果一致。
7. 扩展与延伸
从宏的程序看,只设置了一个试验分组变量。这里可以有读者要问,如果输出表格需要多个试验分组变量,如何处理呢?
我建议,这种情况不使用宏程序,程序不复杂直接手动编写,简洁方便。
宏程序一般处理重复的编程内容,例如,Baseline Demogrphic这类汇总表,有多个不同的分析变量,内容高度重复化,程序中宏程序会调用很多次。但对于多个分组变量的情形,有这样的宏,程序中也只会调用一次。在我看来,这是没有必要的。
当然,对于多个分组变量的亚组分析,每个亚组也可以看成是重复单一的内容。但是,这样简单机械的处理抹去了,各亚组之间的联系。这就像数学的几何题一样,我们可以通过复杂推理得到正确的结果,不过,有时候一条辅助线就可以让整个解题过程简洁的多得多。
这里举个LB频数汇总的例子,LB中可能有二三十个Parameter,如果单独依次出每个Paramter对应的内容,那过程就太过繁琐;如果在宏程序中添加多个分组变量,一次调用就能解决,那跟手动写出完整输出过程又没什么区别。
proc means data = adlb noprint nway completetypes;
by trt01an parcat1n paramn paramcd avisitn;
format anrindn anrindn.;
class anrindn / preloadfmt order = data;
var flag
output n = count out = count1;
run;
当然,这种情况下,如果不了解频数表的输出的逻辑与过程,确实是直接调用宏程序,来得更简单高效一点。
总结
这篇文章介绍了,固定分类顺序的频数汇总表的宏程序输出。固定顺序主要通过,Means过程步的preloadfmt
选项进行实现。
希望对读者日常编程工作,有所帮助。
感谢阅读, 欢迎关注:SAS茶谈!
若有疑问,欢迎评论交流!