欢迎关注,SAS茶谈!
最近使用GTL出了一批线图,基于此,再次梳理了一遍SAS中的GTL语言分组绘图的过程,内容涵盖了大部分GTL出图的设置操作,希望对读者的日常SAS出图工作有所帮助。
GTL的全称是Graph Template Language,是一个建立图形模板,并引用模板进行出图的过程。SAS中的出图工具还有SG系列过程步,与SG过程步相比,GTL在复杂布局和组合绘图方面更具优势和灵活性。
0. 分组绘图的2种实现方式
关于分组,我习惯给2个称呼,区组分组和分析分组 。通常,区组分组是指治疗分组变量,分析分组通常指其他分组变量。这两个称呼没有特别明显的划分,可以粗略类比Means过程步中,by
语句和class
语句。
这篇文章的分组绘图,指的是区组分组,即试验治疗分组。
分组绘图常规的实现方式有两种:
- 为n个试验分组单独新建n个分析变量, 对每一个分析变量进行图形语句输出;
- 使用出图语句中自带的分组选项
group=
。
对于阅读这篇文章的读者,推荐使用第2种方法进行分组绘图。后者程序的简洁性和易读性要大大超过前者!
部分使用第1种方法的人,是为实现分组图形的“错位”,这样操作,需要为每一个试验分组重新单独设置x轴变量值,过程会很繁琐。
第2种方法group=
选项,有对应具体选项实现组间图形的“错位”,非常方便(groupdisplay=cluster clusterwidth=xx
)。
这里插入一段往事,我工作中第一次输出的Figure非常复杂,加之时间紧、任务急,当时整个人有些焦虑。于是,找了一个印度同事类似的Figure代码,想着照葫芦画瓢出一下。代码使用的是第1种方法,当时的区组分组不止3个,分析分组也不止1个,参考的代码结构划分又及其混乱......那张Figure输出过程非常痛苦,印度同事在我心里留下了很不好的印象...
当然,从目的性角度看,不管使用哪种方法,只要能实现输出都是可以的。但是,代码思路、结构最好做到清晰,保证易读性,方便后续的维护、更新与借鉴。
1. SAS绘图概览
临床试验的Figure输出,大体会涉及这5个方面内容:
- 图形的选择以及参数设置
- 坐标轴的设置
- Legend的处理
- 其他显示内容
- 分组属性的设置
出图的第一步,是知晓自己所出的图形。对应的,需要认识用于出图的变量属性。
这次演示的图形为Mean Plot,用于查看不同组数据的均值是否有变化。y轴通常为为分组的均值,x轴通常为分析分组的各个类别。
Mean Plot一般由散点图和折线图构成,散点图会配上误差线(Error Bar)。常见的误差线有95%的可信区间、分位数Q1Q3、Mean±SD等。若图中没有明确的误差线说明,需要跟统计师进行确认。这里我以分位数Q1Q3进行演示。
2. 生成演示数据
为了方便演示,我制作了一个模拟数据集。400位受试者分成3组(trt01an = 1, 2, 3),经历4个访视(avisitn = 0, 1, 2, 3)。对于受试者的访视检测值,给予一定的趋势变化。
***Get data for analysis;
**1. Data for BigN;
data adsl(drop=ia);
do ia = 1 to 400;
usubjid = put(ia, z3.);
trt01an = 1 + (ia>140) + (ia>270);
*Var for count;
flag = 1;
output;
end;
run;
**2. Raw data for figure output;
data adb;
set adsl;
do avisitn = 0 to 3;
if trt01an = 1 then aval = rand('normal', 77, 77) + rand('normal', 20, 20)*avisitn*rand('uniform',1,2);
if trt01an = 2 then aval = rand('normal', 77, 77) + rand('normal', 10, 10)*avisitn*rand('uniform',1,2);
if trt01an = 3 then aval = rand('normal', 77, 77) - rand('normal', 10, 10)*avisitn*rand('uniform',1,2);
output;
end;
run;
模拟数据中,正态分布的标准差给的很大,这是为了使误差线显示明显。
3. 统计量的计算
在出图之前,需要计算输出统计量。从图形实例看,我们需要各试验分组人数(N),各访视节点参与检测的人数(n),以及每个区组分组、每个分析分组的检测值均值(mean)以及对应的分位数(Q1、Q3)。下面介绍各个统计量的计算。
3.1 Format设置
一般来讲,如果一个试验分组人数为0,该试验分组的线图是不需要输出的。而对于部分访视值缺失的情况,小n是需要补0的。输出完整的分组计数,我使用的Means过程步中的preloadfmt
选项,需要提前设置分组变量的Format,具体介绍参考:SAS编程:频数汇总时如何处理分析分组种类不全的情况?。
***Create format for group var;
proc format;
value trt01an
1 = 1
2 = 2
3 = 3
;
value avisitn
0 = 0
1 = 1
2 = 2
3 = 3
;
run;
3.2. 计算BigN
Legend含有BigN的信息,先计算bign,然后构建显示Legend内容的Format。为方便引用,将BigN数值保存到宏变量中,如果对数据不熟悉,可以生成一个N_check
数据集,便于查看BigN宏变量的内容。
***Calculate statistics for figure output;
**1. Derive N in legend;
proc means data = adsl nway completetypes;
format trt01an trt01an.;
class trt01an/ preloadfmt order = data;
var flag;
output n = bign out = bign;
run;
data _null_;
set bign;
call symputx("N_"||strip(put(trt01an, best.)), strip(put(bign, best.)) );
run;
data N_check;
set sashelp.vmacro;
where index(name, "N_");
run;
*Create format for legend display;
proc format;
value trt_dis(notsorted)
1 = "Placebob (N = &N_1.)"
2 = "Treatment A (N = &N_2.)"
3 = "Treetment B (N = &N_3.)"
;
run;
3.2 计算小n及分位数
小n的计算需要输出完整的分析分组的信息(avisitn,class
语句),对于缺失的试验分组不需要显示(trt01an,by
语句)。严格来讲,后面输出figure需要判断是否有试验分组缺失,进而调整输出语句,对于模拟数据我就省事不判断了。
输出统计量的精度最好固定,这样避免精度对QC结果的影响。
**2. Derive mean and q1q3;
proc means data = adb nway completetypes;
by trt01an;
format avisitn avisitn.;
class avisitn / preloadfmt order = data;
var aval;
output out = stat1 n = n mean=mean q1=q1 q3=q3 ;
run;
data stat;
length legend $30;
set stat1;
legend = put(trt01an, trt_dis.);
mean = round(mean, 0.001);
q1 = round(q1, 0.001);
q3 = round(q3, 0.001);
drop _:;
run;
统计量计算完毕后,下面正式使用GTL输出Figure。
4. GTL语言Figure的输出
演示过程为常规的编程流程。
4.1 模板的建立与引用
先用最基础的Plot语句将图形的主体部分输出来:
**1. Create template for figure;
proc template;
define statgraph plot;
begingraph;
layout overlay;
scatterplot y=mean x=avisitn/ name="scatter" yerrorlower=q1 yerrorupper=q3 group = trt01an ;
seriesplot y=mean x=avisitn / name="series" group=trt01an ;
endlayout;
endgraph;
end;
run;
**2. render the template;
proc sgrender data=stat template=plot;
format trt01an trt_dis.;
run;
默认情况下,组与组的图形是重合在一起的,即默认groupdisplay=stack
。在使用cluster
选项值且调试好合适的组间距(groupdisplay=cluster clusterwidth=0.2
),就可以实现错位展示。输出结果如下:
4.2 坐标轴的内容设置
坐标轴内容的设置一般有3个:
- Label的显示
- 首尾留白区域
- 刻度设置以及对应内容显示
坐标轴的Label通过label=
选项设置,一般格式选默认,若需要格式设置,在labelattrs=( )
中设置,具体属性参考SAS官方文档。
上一张输出的x轴前后留白区域看起来有些大,可以通过offsetmin=
、offsetmax=
选项进行设置。调试的原则,是怎么美观怎么调。有一点要注意,留白区域过大可能造成坐标轴刻度内容显示不全。
坐标轴的刻度设置通过linearopts=()
选项设置。坐标轴刻度分为两类,一类是连续型刻度,一类是离散型刻度。两者对应的选项分别为tickvaluesequence=(start= end= increment=)
、tickvaluelist=( )
。一般也会对应设置好,坐标刻度的显示范围,viewmin= viewmax=
。离散型坐标轴刻度对应的说明内容,可以通过选项tickdisplaylist=( )
设置。
演示代码如下:
**1. Create template for figure;
proc template;
define statgraph plot;
begingraph;
layout overlay/
yaxisopts=(
label="Actual Value" offsetmax = 0.2
linearopts=(viewmin=-50 viewmax=300 tickvaluesequence=(start=-50 end=300 increment=50))
)
xaxisopts=(
label="Analysis Visit" offsetmin = 0.05 offsetmax = 0.06
linearopts=(viewmin=0 viewmax=3 tickvaluelist=(0 1 2 3 ) tickdisplaylist=("Baseline" "Visit 1" "Visit 2" "Visit 3") )
);
scatterplot y=mean x=avisitn/ name="scatter" yerrorlower=q1 yerrorupper=q3 group = trt01an groupdisplay=cluster clusterwidth=0.2;
seriesplot y=mean x=avisitn / name="series" group=trt01an groupdisplay=cluster clusterwidth=0.2;
endlayout;
endgraph;
end;
run;
**2. render the template;
proc sgrender data=stat template=plot;
format trt01an trt_dis.;
run;
输出内容如下:
坐标轴内容设置完毕,基本达到美观效果。坐标轴的设置除了以上介绍的内容外,还有一些其他选项,例如添加小坐标、坐标翻转等。具体细节参考GTL官方文档(SAS Help Center: SAS 9.4 Graph Template Language: Reference, Fifth Edition),这里就不详细介绍了。
4.3 Legend的处理
上一张输出的y轴上方留有不小的空白位置,这一片区域是预留给Legend的位置。Legend,图例说明,一般用于说明图形属性与分组的对应关系。
因为这张Figure是散点图和折线图的结合,图例的设置也需要将“点与线”相结合。先看一下常规使用的Legend语句的效果如何?
**1. Create template for figure;
proc template;
define statgraph plot;
begingraph;
layout overlay/
yaxisopts=(
label="Actual Value" offsetmax = 0.2
linearopts=(viewmin=-50 viewmax=300 tickvaluesequence=(start=-50 end=300 increment=50))
)
xaxisopts=(
label="Analysis Visit" offsetmin = 0.05 offsetmax = 0.06
linearopts=(viewmin=0 viewmax=3 tickvaluelist=(0 1 2 3 ) tickdisplaylist=("Baseline" "Visit 1" "Visit 2" "Visit 3") )
);
scatterplot y=mean x=avisitn/ name="scatter" yerrorlower=q1 yerrorupper=q3 group = trt01an groupdisplay=cluster clusterwidth=0.2;
seriesplot y=mean x=avisitn / name="series" group=trt01an groupdisplay=cluster clusterwidth=0.2;
discretelegend "scatter" "series"/ title="" location=inside halign=right valign=top across=1 border=false valueattrs=(size=7pt);
endlayout;
endgraph;
end;
run;
**2. render the template;
proc sgrender data=stat template=plot;
format trt01an trt_dis.;
run;
结果如下:
可以看到点图和线图的图例是分开显示的,对于这张图形来讲,是不合适的。点与线的Legend需要结合起来,通过mergedlegend
语句实现。
mergedlegend "scatter" "series"/ title="" location=inside halign=right valign=top across=1 border=false valueattrs=(size=7pt);
输出图形如下:
有关GTL Legend的其他内容,可以参考文章:
4.4 其他内容的显示
与文章开头展示的样图相比,目前图形还缺少以下内容:Title,footnotes,以及分组小n的显示。
4.4.1 Title
图形的Title直接使用entrytitle
语句,通常不需要修改默认属性。
*titles;
entrytitle "Mean Plot";
4.4.2 Footnotes
图形的Footnote使用entryfootnote
语句,通常是需要设置文字内容属性的。为避免与图形内容太近,通常还会添加空行(entryfootnote " ";
)。
entryfootnote " ";
entryfootnote halign=left "Program: gtl_mean_plot.sas" /pad=(top=10px) textattrs=(size=9pt style=italic);
entryfootnote halign=left "Output: Mean_plot.rtf (Date Generated: &sysdate &systime)"/ textattrs=(size=9pt style=italic);
正常项目中,一些footnotes有可能保存在宏变量中,这时候也需要将其添加到图形中,通过宏循环进行实现。(这里就不新建完整宏程序进行演示了)
*footnotes;
entryfootnote " ";
%if &num_footnots > 0 %then %do ia = 1 %to &num_footnotes;
entryfootnote halign=left "&&foot&ia."/textattrs=(size=9pt);
%end;
entryfootnote halign=left "Program: gtl_mean_plot.sas" /pad=(top=10px) textattrs=(size=9pt style=italic);
entryfootnote halign=left "Output: Mean_plot.rtf (Date Generated: &sysdate &systime)"/ textattrs=(size=9pt style=italic);
4.4.3 小n的显示
小n区域的设置,首先通过innermargin; endinnermargin;
模块,开辟一个嵌套区域进行显示。具体文字的罗列,可以通过blockplot
语句或axistable
语句实现。不过,两者实现的方式有一些区别。
blockplot
语句使用分组处理时,每一组别的文字属性无法单独设置。如果想要每一组的内容属性不同,需要新建新的分析变量(第1种方法)进行拆分处理。同时,首行的文字内容(Number of Subjects (n):)也需要新建一个变量进行保存,使用一个单独blockplot
语句进行输出。暂不接介绍blockplot
语句的实现过程。
axistable
语句有单独的颜色分组控制选项colorgroup=
,同时,首行文字也可以通过选项headerlabel=
进行输出。
演示代码如下:
**1. Create template for figure;
proc template;
define statgraph plot;
begingraph;
layout overlay/
yaxisopts=(
griddisplay=off label="Actual Value" offsetmax = 0.2
linearopts=(viewmin=-50 viewmax=300 tickvaluesequence=(start=-50 end=300 increment=50))
)
xaxisopts=(
griddisplay=off label="Analysis Visit" offsetmin = 0.05 offsetmax = 0.06
linearopts=(viewmin=0 viewmax=3 tickvaluelist=(0 1 2 3 ) tickdisplaylist=("Baseline" "Visit 1" "Visit 2" "Visit 3") )
);
scatterplot y=mean x=avisitn/ name="scatter" yerrorlower=q1 yerrorupper=q3 group = trt01an groupdisplay=cluster clusterwidth=0.1;
seriesplot y=mean x=avisitn / name="series" group=trt01an groupdisplay=cluster clusterwidth=0.1;
mergedlegend "scatter" "series"/ title="" location=inside halign=right valign=top across=1 border=false valueattrs=(size=7pt);
*small n;
innermargin /align=bottom;
axistable x=avisitn value=n / name="division" headerlabel="Number of Subjects (n):" headerlabelattrs=GraphLabelText
valueattrs=(size=9pt ) colorgroup=trt01an class=trt01an;
endinnermargin;
endlayout;
*titles;
entrytitle "Mean Plot";
*footnotes;
entryfootnote " ";
entryfootnote halign=left "Program: gtl_mean_plot.sas" /pad=(top=10px) textattrs=(size=9pt style=italic);
entryfootnote halign=left "Output: Mean_plot.rtf (Date Generated: &sysdate &systime)"/ textattrs=(size=9pt style=italic);
endgraph;
end;
run;
**2. render the template;
proc sgrender data=stat template=plot;
format trt01an trt_dis.;
run;
这样输出,有一个小问题,小n左侧显示文字信息沿用了trt01an的format,与Legend内容相同,想显得冗余。
如果小n左侧信息想要显示其他内容,可以新建一个变量作为分组变量,其值等于trt01an(等于其他有序数值也行),分组变量值的Label设置成想要显示的内容,axistable
语句中的class=
选项值替换为新变量。
proc format;
value nclass
1 = "1:"
2 = "2:"
3 = "3:"
;
run;
data stat_dis;
set stat;
nclass = trt01an;
run;
proc sgrender data=stat_dis template=plot;
format trt01an trt_dis.;
format nclass nclass.;
run;
输出内容如下,小n左侧信息得到更新:
4.5 分组属性的设置
以上输出的点、线、数值的属性都是由SAS系统自动分配,直观上看起来并不美观,那如何为每个不同组别的输出内容进行属性设置呢?
这需要通过DiscreteAttrmap
和DiscreteAttrvar
语句来实现。
DiscreteAttrmap
语句是用于创建一个属性映射,该属性映射将图形属性与离散值(分组类别值)匹配,即属性映射可以与图中的分类变量相关联。这里需要注意,如果分组变量有Format,对应匹配值是具体的Format值。
可以关联的属性有以下4类:
- Fillattrs
- Lineattrs
- Markerattrs
- Textattrs
DiscreteAttrvar
语句是用来在已定义的属性映射和包含对应分类值的分类变量之间创建一个关联。attrvar=
选项指定属性映射的关联名称,var=
选项指定数据集中的分组变量,attrmap=
指定已经定义好的属性映射名称。
这里附上一些属性设置内容,参考:SAS Help Center: Attributes Available for the Attribute Options。
符号的设置:
线型的设置:
颜色的设置:
可以运行以下程序查看SAS支持的颜色命名:
proc registry list
startat="COLORNAMES";
run;
常用颜色如下图:
属性映射的定义与关联是不在layout overlay; endlayout
模块中的,具体的代码实现如下:
**1 Create template for figure;
proc template;
define statgraph plot;
begingraph;
*Attributes for each group var level;
discreteattrmap name = "symbols" / ignorecase = true;
value "Placebob (N = &N_1.)" / markerattrs=(color = blue symbol = circle);
value "Treatment A (N = &N_2.)" / markerattrs=(color = red symbol = triangle);
value "Treetment B (N = &N_3.)" / markerattrs=(color = green symbol = x);
enddiscreteattrmap;
discreteattrmap name = "lines" / ignorecase = true;
value "Placebob (N = &N_1.)" / lineattrs=(color = blue pattern = 1);
value "Treatment A (N = &N_2.)" / lineattrs=(color = red pattern = 4);
value "Treetment B (N = &N_3.)" / lineattrs=(color = green pattern = 14);
enddiscreteattrmap;
discreteattrmap name = "values" / ignorecase = true;
value "1:" / textattrs=(color = blue);
value "2:" / textattrs=(color = red );
value "3:" / textattrs=(color = green);
enddiscreteattrmap;
*Associate the attribute map with group var;
discreteattrvar attrvar = groupmarkers var=trt01an attrmap="symbols";
discreteattrvar attrvar = grouplines var=trt01an attrmap="lines";
discreteattrvar attrvar = groupvalues var=nclass attrmap="values";
layout overlay/
yaxisopts=(
griddisplay=off label="Actual Value" offsetmax = 0.2
linearopts=(viewmin=-50 viewmax=300 tickvaluesequence=(start=-50 end=300 increment=50))
)
xaxisopts=(
griddisplay=off label="Analysis Visit" offsetmin = 0.05 offsetmax = 0.06
linearopts=(viewmin=0 viewmax=3 tickvaluelist=(0 1 2 3 ) tickdisplaylist=("Baseline" "Visit 1" "Visit 2" "Visit 3") )
);
scatterplot y=mean x=avisitn/ name="scatter" yerrorlower=q1 yerrorupper=q3 group = groupmarkers groupdisplay=cluster clusterwidth=0.1;
seriesplot y=mean x=avisitn / name="series" group=grouplines groupdisplay=cluster clusterwidth=0.1;
mergedlegend "scatter" "series"/ title="" location=inside halign=right valign=top across=1 border=false valueattrs=(size=7pt);
*small n;
innermargin /align=bottom;
axistable x=avisitn value=n / name="division" headerlabel="Number of Subjects (n):" headerlabelattrs=GraphLabelText
valueattrs=(size=9pt ) colorgroup=groupvalues class=groupvalues;
endinnermargin;
endlayout;
*titles;
entrytitle "Mean Plot";
*footnotes;
entryfootnote " ";
entryfootnote halign=left "Program: gtl_mean_plot.sas" /pad=(top=10px) textattrs=(size=9pt style=italic);
entryfootnote halign=left "Output: Mean_plot.rtf (Date Generated: &sysdate &systime)"/ textattrs=(size=9pt style=italic);
endgraph;
end;
run;
**2. render the template;
proc sgrender data=stat_dis template=plot;
format trt01an trt_dis.;
format nclass nclass.;
run;
代码运行,输出完毕:
总结
这篇文章以Mean Plot为例,梳理了GTL输出分组绘图的过程。最为关键的一步是,选择group=
选项进行分组输出,组与组的输出错位通过groupdisplay=cluster
实现。后续介绍了,GTL中坐标轴的设置、Legend的处理、其他显示内容、分组属性的设置,涵盖了GTL出图的大部分设置内容。
感谢阅读, 欢迎关注:SAS茶谈!
若有疑问,欢迎评论交流!