I am currently drawing a forest plot and I found a macro can do what I needed.
However, the template provided in this macro has one limitation (The paper was writtern by Janette Garner, title: An Enhanced Forest Plot Macro Using SAS).
The plot looks like as below. As I want to separate the header part and main contents of the plot, I do not know how to fix this. The default option for the header is to paint it with grey color, however I only need a bottom line for the header and make it looks a bit fomal for printing.
Does anyone who knows any solution for this purpose.
Thanks in advance!
proc import datafile="Y:\tab1.csv" dbms=csv out=tab1(where=(^missing(STATORD))) replace;
getnames=yes;
run;
data forest;
set tab1;
by sortord statord;
retain y y1 y2 y3 0;
if first.sortord then do;
y = y2 + 1;
y1 = y - 1.5;
y2 = y - 0.5;
end;
/* Adding 2.5 to each y1 and y2 will insert a small space (of size 0.5) between each subgroup chunk */
y1 = y1 + 2.5;
y2 = y2 + 2.5;
y3 = mean(y1,y2);
run;
data plot;
set forest;
by y; /* Y-values associated with the header are only plotted once */
if not first.y then y = .; /* We assign the variables "zero" and "one" as dummy variables to allow for
plotting of text in some of the columns, in particular the labels. */
zero = 0;
one = 0.1;
/* For alternating groups, add variables so that a reference band can be
generated to aid in visualization */
/* NOTE: To "disable" the reference bands, the user can set the color to
white (cxffffff) in the PROC SGRENDER in Step 3 */
if mod(sortord,2) = 0 then do;
ref1=y1;
ref2=y2;
/* Third reference line to fill in any whitespace that may appear */
if y ^ = . then ref3 = y;
else if y = . then ref3 = ref1 - 0.5;
end;
*** Shift the "Overall" label down ***;
if group = "Overall" then y = y3;
run;
%macro forest( showcol2=Y,
showcol3=Y,
showcol4=Y,
showcol5=Y,
colwghts=%str(0.15 0.32 0.10 0.35 0.08),
barval=%str(0 10 20 30 40 50 60 70 80 90 100),
xlabbar=%bquote(Responders (%)),
showpct=N,
pctfmt=5.1,
frsttype=DIFF/*RATIO*/,
frstval=%str(-30 -20 -10 0 10 20 30)/*%str(0 0.5 1 2)*/,
frstlabl=Y,
xlabfrst=%bquote(Difference (%)),
legtitle=%str(Treatment Group),
ci_col=N,
statlabl=%str(P-value)
);
data plot;
set plot;
%if &frsttype=DIFF %then %do;
ci_txt = strip(put(est,5.1))||"% ("||strip(put(low,5.1))||"% to"||strip(put(high,5.1))||"%)";
%end;
%if &frsttype=RATIO %then %do;
ci_txt = strip(put(est,5.1))||" ("||strip(put(low,5.1))||" to"||strip(put(high,5.1))||")";
%end;
n_txt1p = strip(n_txt1)||" ("||strip(put(percent1,&pctfmt))||"%)";
n_txt2p = strip(n_txt2)||" ("||strip(put(percent2,&pctfmt))||"%)";
run;
proc sql noprint;
select max(y2) into :ymax from plot;
quit;
%let colcnt = %eval(%sysfunc(count(&showcol2&showcol3&showcol4&showcol5,Y))+1);
proc template;
define statgraph ForestPlot;
*** Assign dynamic variables, where the options are specified in the SGRENDER procedure ***;
dynamic _headercolor _trt1labl _trt1color _trt2labl _trt2color _refcolor
_refsize _barsize _ymin _ymax _title _footnote;
begingraph;
*** Add a title, if one is specified in the SGRENDER procedure ***;
EntryTitle _title /;
layout lattice / columns=&colcnt columnweights=(&colwghts);
*** Specify column headers ***;
sidebar / align=top;
layout lattice / rows=1 columns=&colcnt columnweights=(&colwghts)
backgroundcolor=_headercolor opaque=true;
entry halign=center textattrs=(size=8 weight=bold) "";
%if &showcol2=Y %then %do;
entry halign=center textattrs=(size=8 weight=bold) "";
%end;
%if &showcol3=Y %then %do;
entry halign=center textattrs=(size=8 weight=bold)
%if &showpct=N %then %do;
"n/N" %end; %else %do; "n/N (%)"
%end;
;
%end;
%if &showcol4=Y %then %do;
entry halign=center textattrs=(size=8 weight=bold) "";
%end;
%if &showcol5=Y %then %do;
entry halign=center textattrs=(size=8 weight=bold) "&statlabl" ;
%end;
endlayout;
endsidebar;
*** Column 1: Labels of the comparisons ***;
layout overlay / walldisplay=none xaxisopts=(display=none) yaxisopts=(reverse=true display=none
tickvalueattrs=(weight=bold) linearopts=(viewmin=_ymin viewmax=_ymax));
referenceline y=ref1 / lineattrs=(thickness=_refsize color=_refcolor);
referenceline y=ref2 / lineattrs=(thickness=_refsize color=_refcolor);
referenceline y=ref3 / lineattrs=(thickness=_refsize color=_refcolor);
*** Header, using "y" variable ***;
highlowplot y=y low=zero high=zero / highlabel=group lineattrs=(thickness=0) labelattrs=(size=7 weight=bold);
*** Subgroup, using "y3" variable ***;
highlowplot y=y3 low=one high=one / highlabel=level lineattrs=(thickness=0) labelattrs=(size=7 weight=bold);
endlayout;
%if &showcol2=Y %then %do;
*** Column 2: Barplots of the proportion of successes in each treatment group ***;
layout overlay / xaxisopts=(label="&xlabbar" labelattrs=(size=7 weight=bold)
linearopts=(tickvaluepriority=true tickvaluelist=(&barval))
/*display=(tickvalues)*/) /* This hides the x-axis label */
yaxisopts=(reverse=true display=none linearopts=(viewmin=_ymin viewmax=_ymax)) walldisplay=none;
referenceline y=ref1 / lineattrs=(thickness=_refsize color=_refcolor);
referenceline y=ref2 / lineattrs=(thickness=_refsize color=_refcolor);
referenceline y=ref3 / lineattrs=(thickness=_refsize color=_refcolor);
*** The barplots are created using a HIGHLOW plot, where the thickness of the line is
controlled by user. Tweaking is required based on the number of rows, height of the
output and spacing between bars. ***;
highlowplot y=y1 low=zero high=percent1 /lineattrs=(thickness=_barsize color=_trt1color)
labelattrs=(size=7 weight=bold) name='trt1' legendlabel=_trt1labl;
highlowplot y=y2 low=zero high=percent2 /lineattrs=(thickness=_barsize color=_trt2color)
labelattrs=(size=7 weight=bold) name='trt2' legendlabel=_trt2labl;
referenceline x=0;
endlayout;
%end;
%if &showcol3=Y %then %do;
*** Column 3: Column of values that support the barplots, n/N ***;
layout overlay / walldisplay=none xaxisopts=(display=none) yaxisopts=(reverse=true display=none
tickvalueattrs=(weight=bold) linearopts=(viewmin=_ymin viewmax=_ymax));
referenceline y=ref1 / lineattrs=(thickness=_refsize color=_refcolor);
referenceline y=ref2 / lineattrs=(thickness=_refsize color=_refcolor);
referenceline y=ref3 / lineattrs=(thickness=_refsize color=_refcolor);
highlowplot y=y1 low=zero high=zero /
%if &showpct=N %then %do;
highlabel=n_txt1
%end;
%if &showpct=Y %then %do;
highlabel=n_txt1p
%end;
lineattrs=(thickness=0) labelattrs=(size=7 color=_trt1color);
highlowplot y=y2 low=zero high=zero /
%if &showpct=N %then %do;
highlabel=n_txt2
%end;
%if &showpct=Y %then %do;
highlabel=n_txt2p
%end;
lineattrs=(thickness=0) labelattrs=(size=7 color=_trt2color);
endlayout;
%end;
%if &showcol4=Y %then %do;
*** Column 4: Plot the CI for the difference (and include the point estimate) ***;
layout overlay / xaxisopts=(label="&xlabfrst" labelattrs=(size=7 weight=bold)
linearopts=(tickvaluepriority=true tickvaluelist=(&frstval)))
yaxisopts=(reverse=true display=none linearopts=(viewmin=_ymin
viewmax=_ymax)) walldisplay=none;
referenceline y=ref1 / lineattrs=(thickness=_refsize color=_refcolor);
referenceline y=ref2 / lineattrs=(thickness=_refsize color=_refcolor);
referenceline y=ref3 / lineattrs=(thickness=_refsize color=_refcolor);
%if &frsttype=DIFF %then %do;
referenceline x=0 / lineattrs=(pattern=shortdash);
%end;
%if &frsttype=RATIO %then %do;
referenceline x=1 / lineattrs=(pattern=shortdash);
%end;
highlowplot y=y3 low=low high=high / lineattrs=(thickness=1 color=black)
%if &frstlabl=Y %then %do;
lowlabel=low highlabel=high
%end;
;
scatterplot y=y3 x=est / markerattrs=(symbol=circlefilled size=10 color=black)
datalabelposition=bottom
%if &frstlabl=Y %then %do;
datalabel=est
%end;
;
*** Insert labels that explain the plot, namely that the right
side favors Treatment 1 and the left side favors Treatment 2. The labels for
these are defined in the SGRENDER procedure. ***;
layout gridded / rows=2 border=false autoalign=(topright);
entry textattrs=(size=7) halign=right 'Favors';
entry textattrs=(size=7 color=_trt1color) halign=right _trt1labl;
endlayout;
layout gridded / rows=2 border=false autoalign=(topleft);
entry textattrs=(size=7) halign=left 'Favors';
entry textattrs=(size=7 color=_trt2color) halign=left _trt2labl;
endlayout;
endlayout;
%end;
%if &showcol5=Y %then %do;
*** Column 5: Statistics (Numerical Values) columns ***;
layout overlay / walldisplay=none xaxisopts=(display=none)
yaxisopts=(reverse=true display=none linearopts=(viewmin=_ymin viewmax=_ymax));
referenceline y=ref1 / lineattrs=(thickness=_refsize color=_refcolor);
referenceline y=ref2 / lineattrs=(thickness=_refsize color=_refcolor);
referenceline y=ref3 / lineattrs=(thickness=_refsize color=_refcolor);
highlowplot y=y3 low=zero high=zero /
%if &ci_col=Y %then %do;
highlabel=ci_txt
%end;
%if &ci_col=N %then %do;
highlabel=stat_txt
%end;
lineattrs=(thickness=0) labelattrs=(size=7);
endlayout;
%end;
endlayout;
%if &showcol2=Y %then %do; /* Only display legend if barplot shown */
*** Add legend to bottom of the graph to explain colored bars ***;
layout globallegend / type=row weights=preferred legendtitleposition=left;
discretelegend 'trt1' 'trt2' / title="&legtitle" titleattrs=(size=8 weight=bold)
across=2 displayclipped=true;
endlayout;
%end;
*** Add footnote text, if specified in the SGRENDER procedure ***;
entryfootnote halign=left textattrs=(size=7) _footnote;
endgraph;
end;
run;
proc sgrender data=plot template=ForestPlot;
dynamic _refcolor='cxf0f0f0' /* Set _refcolor='cxffffff' (white) to "hide" the reference bars */
_refsize=15
_headercolor='cxd0d0d0'
_trt1labl='Switch group'
_trt1color='cxb30437'
_trt2labl='No-switch group'
_trt2color='cx009fc3'
_barsize=11.5
_ymin=2
_ymax=&ymax.
_title=''
_footnote='';
run;
%mend forest;
%forest;
I would use the DRAWLINE statement (see it added to the sidebar below)
sidebar / align=top;
layout lattice / rows=1 columns=&colcnt columnweights=(&colwghts)
backgroundcolor=_headercolor opaque=true;
drawline x1=0 x2=100 y1=0 y2=0 /drawspace=layoutpercent lineattrs=(thickness=1pt color=black pattern=1);
entry halign=center textattrs=(size=8 weight=bold) "";
%if &showcol2=Y %then %do;
entry halign=center textattrs=(size=8 weight=bold) "";
%end;
%if &showcol3=Y %then %do;
entry halign=center textattrs=(size=8 weight=bold)
%if &showpct=N %then %do;
"n/N" %end; %else %do; "n/N (%)"
%end;
;
%end;
%if &showcol4=Y %then %do;
entry halign=center textattrs=(size=8 weight=bold) "";
%end;
%if &showcol5=Y %then %do;
entry halign=center textattrs=(size=8 weight=bold) "&statlabl" ;
%end;
endlayout;
endsidebar;
I would use the DRAWLINE statement (see it added to the sidebar below)
sidebar / align=top;
layout lattice / rows=1 columns=&colcnt columnweights=(&colwghts)
backgroundcolor=_headercolor opaque=true;
drawline x1=0 x2=100 y1=0 y2=0 /drawspace=layoutpercent lineattrs=(thickness=1pt color=black pattern=1);
entry halign=center textattrs=(size=8 weight=bold) "";
%if &showcol2=Y %then %do;
entry halign=center textattrs=(size=8 weight=bold) "";
%end;
%if &showcol3=Y %then %do;
entry halign=center textattrs=(size=8 weight=bold)
%if &showpct=N %then %do;
"n/N" %end; %else %do; "n/N (%)"
%end;
;
%end;
%if &showcol4=Y %then %do;
entry halign=center textattrs=(size=8 weight=bold) "";
%end;
%if &showcol5=Y %then %do;
entry halign=center textattrs=(size=8 weight=bold) "&statlabl" ;
%end;
endlayout;
endsidebar;
Join us for SAS Innovate 2025, our biggest and most exciting global event of the year, in Orlando, FL, from May 6-9. Sign up by March 14 for just $795.
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.