Hi All.
I am trying to create a forest plot. However, I am encountering some issues.
1. one of the texts is long, so I am trying to wrap it so that the actual ratio bars have enough space. I tried with ODSESCAPECHAR, (*ESC*){unicode '000a'x}. But not able to achieve it. Circled Red in Screen shot,
2. How I can control the width of Axis tables? I tried Column weights options but no luck. * Blue lines in Screen shot
proc format;
value subgrp
0 = 'Study Level'
1 = "Psychotic events continuiously Increasing the type.(*ESC*){unicode '000a'x} This text is big it have to wrap so that bars have enough space"
1.1 = '= 10'
1.2 = '>10'
;
run;
data forest;
input Indent Subgroup lsmean1 count1 lsmean2 count2 lsdiff cilower ciupper;
datalines;
0 0 2.06 29 1.03 32 1.02 0.72 1.33
0 1 . . . . . . .
2 1.1 2.1 19 1.17 20 0.93 0.52 1.33
2 1.2 1.93 10 0.83 12 1.09 0.52 1.55
;
run;
data forest2;
length subgroup_ $200 col3-col5 $20;
set forest;
subgroup_=strip(put(subgroup, subgrp.));
indent=ifn(indent eq 2, 1, 0);
obsID=_n_;
if mod(_N_-1, 6) in (1, 2, 3) then ref=obsID;
if indent>0 or subgroup=0 then do;
col3 = strip(put(lsmean1, best.)) || ' ('||strip(put(count1, best.)) ||')';
col4 = strip(put(lsmean2, best.))|| ' ('||strip(put(count2, best.)) ||')';
col5 = strip(put(lsdiff, best.)) || ' ('||strip(put(cilower, best.)) ||', '||strip(put(ciupper, best.))||')';
mean=lsdiff;
low=cilower;
high=ciupper;
end;
run;
ods path(prepend) work.templat(update);
options orientation=landscape;
ods graphics on / imagefmt=png attrpriority=none height=5.1 in width=9 in
border=off ;
proc template;
define statgraph Forest;
dynamic _show_bands _color _thk;
begingraph;
discreteattrmap name='text';
value '0' / textattrs=(weight=bold);
value other;
enddiscreteattrmap;
discreteattrvar attrvar=type var=indent attrmap='text';
layout lattice / columns=1/* columnweights=(0.2 0.4 0.1 0.1 0.1 0.1)*/;
*** left-side table ***;
layout overlay / walldisplay=none xaxisopts=(display=(line tick tickvalues label) label='<------Favors Placebo Favors Treatment----->'
labelAttrs=(size=7 weight=bold )
linearOpts=(tickValuePriority=true
tickValueList=( 0.0 0.5 1.0 1.5 2.0 ))
lineExtent=data)
yaxisopts=(reverse=true display=none
tickvalueattrs=(weight=bold));
*referenceline y=ref / lineattrs=(thickness=_thk color=_color);
innermargin/align=left ;
axistable y=obsID value=subgroup_ / label='Study/Subgroup' indentweight=indent display=(values) textgroup=type;
endinnermargin;
*** graph ***;
referenceline y=ref/ lineattrs=(thickness=_thk color=_color);
scatterplot y=obsID x=mean / xerrorlower=low xerrorupper=high
markerattrs=(symbol=squarefilled);
referenceline x=1;
*** right side table ***;
innermargin/align=right;
*referenceline y=ref / lineattrs=(thickness=_thk color=_color);
axistable y=obsID value=col3 /label='Treatment LS Mean (n)' labelattrs=(size=7pt) ;
axistable y=obsID value=col4 /label='Placebo LS Mean (n)' labelattrs=(size=7pt) ;
axistable y=obsID value=col5 /label='LS Mean Diff (95% CI)' labelattrs=(size=7pt);
endinnermargin;
endlayout;
endlayout; *** end lattice ***;
endgraph;
end;
run;
*** create the graph ***;
ods rtf file = "C:\Users\test.RTF";
proc sgrender data=forest2 template=Forest;
dynamic _color='cxff9797' _thk=15;
run;
ods rtf close;
Thank you
proc format; value subgrp 0 = 'Study Level' 1.1 = "Psychotic events continuiously " 1.2 = "Increasing the type." 1.3 = "This text is big it have to wrap " 1.4= "so that bars have enough space." 2.1 = '= 10' 2.2 = '>10' ; run; data forest; input Indent Subgroup lsmean1 count1 lsmean2 count2 lsdiff cilower ciupper; datalines; 0 0 2.06 29 1.03 32 1.02 0.72 1.33 0 1.1 . . . . . . . 0 1.2 . . . . . . . 0 1.3 . . . . . . . 0 1.4 . . . . . . . 2 2.1 2.1 19 1.17 20 0.93 0.52 1.33 2 2.2 1.93 10 0.83 12 1.09 0.52 1.55 ; run; data forest2; length subgroup_ $200 col3-col5 $20; set forest; subgroup_=strip(put(subgroup, subgrp.)); indent=ifn(indent eq 2, 1, 0); obsID=_n_; if _n_>1 then ref=obsID; if indent>0 or subgroup=0 then do; col3 = strip(put(lsmean1, best.)) || ' ('||strip(put(count1, best.)) ||')'; col4 = strip(put(lsmean2, best.))|| ' ('||strip(put(count2, best.)) ||')'; col5 = strip(put(lsdiff, best.)) || ' ('||strip(put(cilower, best.)) ||', '||strip(put(ciupper, best.))||')'; mean=lsdiff; low=cilower; high=ciupper; end; run; ods path(prepend) work.templat(update); options orientation=landscape; ods graphics on / imagefmt=png attrpriority=none height=3.8 in width=9 in border=off ; proc template; define statgraph Forest; dynamic _show_bands _color _thk; begingraph; discreteattrmap name='text'; value '0' / textattrs=(weight=bold); value other; enddiscreteattrmap; discreteattrvar attrvar=type var=indent attrmap='text'; layout lattice / columns=1/* columnweights=(0.2 0.4 0.1 0.1 0.1 0.1)*/; *** left-side table ***; layout overlay / walldisplay=none xaxisopts=(display=(line ticks tickvalues label) label='<------Favors Placebo Favors Treatment----->' labelAttrs=(size=12 weight=bold ) tickvalueattrs=(size=12) linearOpts=(tickValuePriority=true tickValueList=( 0.0 0.5 1.0 1.5 2.0 )) lineExtent=data) yaxisopts=(reverse=true display=none tickvalueattrs=(weight=bold)); *referenceline y=ref / lineattrs=(thickness=_thk color=_color); innermargin/align=left ; axistable y=obsID value=subgroup_ / label='Study/Subgroup' indentweight=indent display=(values) textgroup=type valueattrs=(size=12); endinnermargin; *** graph ***; referenceline y=ref/ lineattrs=(thickness=_thk color=_color); scatterplot y=obsID x=mean / xerrorlower=low xerrorupper=high markerattrs=(symbol=squarefilled); referenceline x=1; *** right side table ***; innermargin/align=right; *referenceline y=ref / lineattrs=(thickness=_thk color=_color); axistable y=obsID value=col3 /label='Treatment LS Mean (n)' labelattrs=(size=12) valueattrs=(size=12) ; axistable y=obsID value=col4 /label='Placebo LS Mean (n)' labelattrs=(size=12) valueattrs=(size=12); axistable y=obsID value=col5 /label='LS Mean Diff (95% CI)' labelattrs=(size=12) valueattrs=(size=12); endinnermargin; endlayout; endlayout; *** end lattice ***; endgraph; end; run; *** create the graph ***; proc sgrender data=forest2 template=Forest; dynamic _color='cxff9797' _thk=45; run;
You need to use TWO rows or more to split this long text .
Here is an example(proc sgplot) I used before to split a long text into THREE rows.
data forest_subgroup; infile cards truncover expandtabs; label Subgroup='子组' Count='人数' Percent='Mean(SD)' Count_test='人数' Percent_test='Mean(SD)' ci='组间差(95%CI)' pvalue='P值'; input Id Subgroup : $80. Count Percent : $40. Count_test Percent_test : $40. Mean Low High ci :$40. pvalue $; indentWt=1; ObsId=_n_; datalines; 1 总体 332 6.35(3.45) 111 3.73(3.98) 2.62 1.85 3.39 2.62(1.85,3.39) <0.001 1 年龄 . . . . . . . . 2 1~3岁 101 5.80(3.43) 36 3.75(4.38) 2.05 0.63 3.47 2.05(0.63,3.47) 0.005 2 4~7岁 120 6.30(3.69) 37 2.73(3.65) 3.58 2.21 4.95 3.58(2.21,4.95) <0.001 2 8~14岁 111 6.91(3.12) 38 4.69(3.75) 2.22 0.99 3.44 2.22(0.99,3.44) <0.001 1 基线VAS评分 . . . . . . . . 2 ≤6分 78 3.44(2.73) 37 1.31(2.79) 2.13 1.04 3.21 2.13(1.04,3.21) <0.001 2 >6分 254 7.25(3.14) 74 4.94(3.95) 2.31 1.32 3.30 2.31(1.32,3.30) <0.001 1 中位病程 . . . . . . . . 2 中位病程以下 162 6.32(3.79) 63 3.65(4.43) 2.67 1.50 3.83 2.67(1.50,3.83) <0.001 2 中位病程以上 170 6.38(3.09) 48 3.83(3.35) 2.55 1.54 3.56 2.55(1.54,3.56) <0.001 1 剔除使用 . . . . . . . . 1 “未指定的草药和传统药物” 298 6.41(3.22) 107 3.78(4.04) 2.64 1.78 3.49 2.64(1.78,3.49) <0.001 1 的受试者 . . . . . . . . 1 增加01中心 364 6.23(3.36) 120 3.67(3.91) 2.56 1.78 3.35 2.56(1.78,3.35) <0.001 1 年龄(增加01中心) . . . . . . . . 2 1~3岁 104 5.77(3.39) 37 3.65(4.37) 2.12 0.53 3.71 2.12(0.53,3.71) 0.010 2 4~7岁 135 6.23(3.56) 41 2.91(3.52) 3.32 2.07 4.57 3.32(2.07,4.57) <0.001 2 8~14岁 125 6.62(3.08) 42 4.44(3.79) 2.19 1.03 3.34 2.19(1.03,3.34) <0.001 ; run; data forest_subgroup; set forest_subgroup; indentWt=1; ObsId=_n_; run; data forest_subgroup_2; set forest_subgroup nobs=n end=last; length text $20; val=mod(_N_-1, 6); /* if val eq 1 or val eq 2 or val eq 3 then ref=obsid;*/ if obsid in (2:5 9:11 15 ) then ref=obsid; /*--Separate Subgroup headers and obs into separate columns--*/ indentwt=2; if id=1 then indentWt=0; output; if last then do; call missing (subgroup, count, percent, Count_test ,Percent_test, mean, low, high, ci, indentwt, val, ref ,pvalue); obsid=n+1; xl=1; yl=n+1; text='P'; output;; xl=4; yl=n+1; text='T'; output; end; run; /*--Define Format with Unicode for the left and right arrows--*/ proc format;; value $txt "T" = "试验组更好 (*ESC*){Unicode '2192'x}" "P" = "(*ESC*){Unicode '2190'x} 对照组更好"; run; /*--Attribute maps for Subgroup Test attributes--*/ data attrmap; length textweight $10; id='text'; value='1'; textcolor='Black'; textsize=15; textweight='bold'; output; id='text'; value='2'; textcolor='Black'; textsize=10; textweight='normal'; output; run; /*--Forest Plot--*/ options missing=' ' orientation=landscape ; /*ods listing style=htmlblue;*/ title j=l h=1.2 " 试验组 对照组"; /*ods listing gpath='c:\temp\' image_dpi=400;*/ /*ods graphics / reset width=5in height=3in imagename='Subgroup_Forest_SG_94';*/ ods rtf file='c:\temp\temp.rtf' style=htmlblue; ods graphics / reset width=9.6in height=4.5in noborder noscale outputfmt=png; proc sgplot data=forest_subgroup_2 nowall noborder nocycleattrs dattrmap=attrmap noautolegend; format text $txt.; styleattrs axisextent=data; refline ref / lineattrs=(thickness=20 color=grayee); /*<-- discrete thickness*/ highlow y=obsid low=low high=high /lowcap=serif highcap=serif lineattrs=(color=black); /*clipcap CLIPCAPSHAPE=CLOSEDARROW */ scatter y=obsid x=mean / markerattrs=(symbol=squarefilled size=8 color=black ); scatter y=obsid x=mean / markerattrs=(size=0) x2axis; refline 0 / axis=x; text x=xl y=obsid text=text / position=bottom contributeoffsets=none strip textattrs=(size=10); yaxistable subgroup / location=inside position=left textgroup=id labelattrs=(size=10) LABELJUSTIFY=left valueattrs=(size=10 ) textgroupid=text indentweight=indentWt; yaxistable Count Percent Count_test Percent_test ci pvalue/ VALUEJUSTIFY=right location=inside position=left labelattrs=(size=10) valueattrs=(size=10) pad=(right=15px) ; yaxis reverse display=none colorbands=odd colorbandsattrs=(transparency=1) ; xaxis display=(nolabel) values=(0 to 5 by 1); x2axis label='组间差' display=(noline noticks novalues) labelattrs=(size=10) ; run; ods rtf close;
proc format; value subgrp 0 = 'Study Level' 1.1 = "Psychotic events continuiously " 1.2 = "Increasing the type." 1.3 = "This text is big it have to wrap " 1.4= "so that bars have enough space." 2.1 = '= 10' 2.2 = '>10' ; run; data forest; input Indent Subgroup lsmean1 count1 lsmean2 count2 lsdiff cilower ciupper; datalines; 0 0 2.06 29 1.03 32 1.02 0.72 1.33 0 1.1 . . . . . . . 0 1.2 . . . . . . . 0 1.3 . . . . . . . 0 1.4 . . . . . . . 2 2.1 2.1 19 1.17 20 0.93 0.52 1.33 2 2.2 1.93 10 0.83 12 1.09 0.52 1.55 ; run; data forest2; length subgroup_ $200 col3-col5 $20; set forest; subgroup_=strip(put(subgroup, subgrp.)); indent=ifn(indent eq 2, 1, 0); obsID=_n_; if _n_>1 then ref=obsID; if indent>0 or subgroup=0 then do; col3 = strip(put(lsmean1, best.)) || ' ('||strip(put(count1, best.)) ||')'; col4 = strip(put(lsmean2, best.))|| ' ('||strip(put(count2, best.)) ||')'; col5 = strip(put(lsdiff, best.)) || ' ('||strip(put(cilower, best.)) ||', '||strip(put(ciupper, best.))||')'; mean=lsdiff; low=cilower; high=ciupper; end; run; ods path(prepend) work.templat(update); options orientation=landscape; ods graphics on / imagefmt=png attrpriority=none height=3.8 in width=9 in border=off ; proc template; define statgraph Forest; dynamic _show_bands _color _thk; begingraph; discreteattrmap name='text'; value '0' / textattrs=(weight=bold); value other; enddiscreteattrmap; discreteattrvar attrvar=type var=indent attrmap='text'; layout lattice / columns=1/* columnweights=(0.2 0.4 0.1 0.1 0.1 0.1)*/; *** left-side table ***; layout overlay / walldisplay=none xaxisopts=(display=(line ticks tickvalues label) label='<------Favors Placebo Favors Treatment----->' labelAttrs=(size=12 weight=bold ) tickvalueattrs=(size=12) linearOpts=(tickValuePriority=true tickValueList=( 0.0 0.5 1.0 1.5 2.0 )) lineExtent=data) yaxisopts=(reverse=true display=none tickvalueattrs=(weight=bold)); *referenceline y=ref / lineattrs=(thickness=_thk color=_color); innermargin/align=left ; axistable y=obsID value=subgroup_ / label='Study/Subgroup' indentweight=indent display=(values) textgroup=type valueattrs=(size=12); endinnermargin; *** graph ***; referenceline y=ref/ lineattrs=(thickness=_thk color=_color); scatterplot y=obsID x=mean / xerrorlower=low xerrorupper=high markerattrs=(symbol=squarefilled); referenceline x=1; *** right side table ***; innermargin/align=right; *referenceline y=ref / lineattrs=(thickness=_thk color=_color); axistable y=obsID value=col3 /label='Treatment LS Mean (n)' labelattrs=(size=12) valueattrs=(size=12) ; axistable y=obsID value=col4 /label='Placebo LS Mean (n)' labelattrs=(size=12) valueattrs=(size=12); axistable y=obsID value=col5 /label='LS Mean Diff (95% CI)' labelattrs=(size=12) valueattrs=(size=12); endinnermargin; endlayout; endlayout; *** end lattice ***; endgraph; end; run; *** create the graph ***; proc sgrender data=forest2 template=Forest; dynamic _color='cxff9797' _thk=45; run;
Thank you very much @Ksharp . you are Awesome. I have a question: what is the usual way we display if the upper limit of CI is beyond the Scale we display in the figure? For example, my upper limit CI is 25 for " >10 "row, which will be difficult to display due to space. Can we add, like, an arrow instead of Caps or something to the upper limit?
You need the help from option "clipcap=true CLIPCAPSHAPE=CLOSEDARROW" of highlow statement.
proc format; value subgrp 0 = 'Study Level' 1.1 = "Psychotic events continuiously " 1.2 = "Increasing the type." 1.3 = "This text is big it have to wrap " 1.4= "so that bars have enough space." 2.1 = '= 10' 2.2 = '>10' ; run; data forest; input Indent Subgroup lsmean1 count1 lsmean2 count2 lsdiff cilower ciupper; datalines; 0 0 2.06 29 1.03 32 1.02 0.72 1.33 0 1.1 . . . . . . . 0 1.2 . . . . . . . 0 1.3 . . . . . . . 0 1.4 . . . . . . . 2 2.1 2.1 19 1.17 20 0.93 0.52 1.33 2 2.2 1.93 10 0.83 12 1.09 0.52 12.55 ; run; data forest2; length subgroup_ $200 col3-col5 $20; set forest; subgroup_=strip(put(subgroup, subgrp.)); indent=ifn(indent eq 2, 1, 0); obsID=_n_; if _n_>1 then ref=obsID; if indent>0 or subgroup=0 then do; col3 = strip(put(lsmean1, best.)) || ' ('||strip(put(count1, best.)) ||')'; col4 = strip(put(lsmean2, best.))|| ' ('||strip(put(count2, best.)) ||')'; col5 = strip(put(lsdiff, best.)) || ' ('||strip(put(cilower, best.)) ||', '||strip(put(ciupper, best.))||')'; mean=lsdiff; low=cilower; high=ciupper; end; run; ods path(prepend) work.templat(update); options orientation=landscape; ods graphics on / imagefmt=png attrpriority=none height=3.8 in width=9 in border=off ; proc template; define statgraph Forest; dynamic _show_bands _color _thk; begingraph; discreteattrmap name='text'; value '0' / textattrs=(weight=bold); value other; enddiscreteattrmap; discreteattrvar attrvar=type var=indent attrmap='text'; layout lattice / columns=1/* columnweights=(0.2 0.4 0.1 0.1 0.1 0.1)*/; *** left-side table ***; layout overlay / walldisplay=none xaxisopts=(display=(line ticks tickvalues label) label='<------Favors Placebo Favors Treatment----->' labelAttrs=(size=12 weight=bold ) tickvalueattrs=(size=12) linearOpts=(tickValuePriority=true viewmax=2 viewmin=0 TICKVALUEPRIORITY=false tickValueList=( 0.0 0.5 1.0 1.5 2.0 )) lineExtent=data ) yaxisopts=(reverse=true display=none tickvalueattrs=(weight=bold)); *referenceline y=ref / lineattrs=(thickness=_thk color=_color); innermargin/align=left ; axistable y=obsID value=subgroup_ / label='Study/Subgroup' indentweight=indent display=(values) textgroup=type valueattrs=(size=12); endinnermargin; *** graph ***; referenceline y=ref/ lineattrs=(thickness=_thk color=_color); scatterplot y=obsID x=mean /markerattrs=(symbol=squarefilled); highlowplot y=obsID low=low high=high /lowcap=serif highcap=serif lineattrs=(color=black) clipcap=true CLIPCAPSHAPE=CLOSEDARROW ; referenceline x=1; *** right side table ***; innermargin/align=right; *referenceline y=ref / lineattrs=(thickness=_thk color=_color); axistable y=obsID value=col3 /label='Treatment LS Mean (n)' labelattrs=(size=12) valueattrs=(size=12) ; axistable y=obsID value=col4 /label='Placebo LS Mean (n)' labelattrs=(size=12) valueattrs=(size=12); axistable y=obsID value=col5 /label='LS Mean Diff (95% CI)' labelattrs=(size=12) valueattrs=(size=12); endinnermargin; endlayout; endlayout; *** end lattice ***; endgraph; end; run; *** create the graph ***; proc sgrender data=forest2 template=Forest; dynamic _color='cxff9797' _thk=45; run;
Thank you very much.
Good news: We've extended SAS Hackathon registration until Sept. 12, so you still have time to be part of our biggest event yet – our five-year anniversary!
Learn how use the CAT functions in SAS to join values from multiple variables into a single value.
Find more tutorials on the SAS Users YouTube channel.
Ready to level-up your skills? Choose your own adventure.