Dear all,
I followed the example from SAS to create a forest plot.
But there seems a "double-space" condition in my graphic.
Would you please help me find out the problem is?
Thank you very much!
Please find the attachment for my graphic, and
Here is my program:
/*--Add "Id" to identify methods headings from values --*/
data forest;
input Id methods $3-20 Countpct $22-40 HR lci hci;
indentWt=1;
ObsId=_n_;
datalines;
1 Primary analysis 0.64 (0.43-0.95) 0.64 0.43 0.95
1 Grace period . . . .
2 60 days 0.70 (0.49-1.01) 0.7 0.49 1.01
2 90 days 1.06 (0.86-1.32) 1.06 0.86 1.32
1 Age groups . . . .
2 <=65 years 0.43 (0.19-0.93) 0.43 0.19 0.93
2 >65 years 0.69 (0.44-1.08) 0.69 0.44 1.08
1 Gender . . . .
2 Female 0.69 (0.43-1.10) 0.69 0.43 1.1
2 Male 0.57 (0.28-1.15) 0.57 0.28 1.15
1 CVD history . . . .
2 Without 0.69 (0.43-1.08) 0.69 0.43 1.08
2 With 0.50 (0.24-1.06) 0.5 0.24 1.06
;
run;
data forest2;
set forest;
/*if count ne . then CountPct=put(count, 4.0) || "(" || put(percent, 3.0) || ")";*/
if mod(obsId-1, 6) in (1, 2, 3) then ref=obsId;
/* Reduce indent for methods heading */
if id=1 then indentWt=0;
run;
/**
* Forest plot template using a one-column outermost lattice layout.
* The header uses a nested lattice layout in the top side bar.
* An overlay layout makes up the main area belci it.
* The left side table of values are axis tables placed inside
* inner margin of the overlay layout.
*/
proc template;
define statgraph forestAxisTable;
dynamic _bandColor _headerColor;
begingraph;
discreteattrmap name='text' / trimleading=true;
value '1' / textAttrs=(size=7 weight=bold);
value '2' / textAttrs=(color=gray size=6 weight=normal);
enddiscreteattrmap;
discreteattrvar attrvar=txtDAV var=id attrmap='text';
layout lattice / columns=1;
/*--Column headers--*/
sidebar / align=top;
layout lattice / columns=2 rowweights=uniform columnweights=(0.35 0.65)
backgroundcolor=_headerColor opaque=true;
entry textAttrs=(size=8 weight=bold) hAlign=left "Methods"
hAlign=right " HR (95% CI)";
entry textAttrs=(size=8 weight=bold) "Hazard Ratio";
endLayout;
endsidebar;
/* Single cell with inner margins for left and right tables */
layout overlay / xAxisOpts=(display=(line ticks tickvalues)
tickValueAttrs=(size=6 weight=bold)
labelAttrs=(size=6 weight=bold)
linearOpts=(tickValuePriority=true
tickValueList=(0.0 0.5 1.0 1.5 2.0))
offsetMin=0.1
)
yAxisOpts=(reverse=true display=none offsetMin=0) wallDisplay=none;
/* Left-side table */
innerMargin / align=left gutter=0.1in;
axisTable y=obsId value=Methods / textGroup=txtDAV
indentWeight=indentwt display=(values);
axisTable y=obsId value=CountPct / display=(values);
endInnerMargin;
/* Odds Ratio plot */
referenceLine y=ref / lineAttrs=(thickness=15 color=_bandColor);
scatterPlot y=obsId x=HR / markerAttrs=(symbol=squareFilled)
xErrorLower=LCI xErrorUpper=HCI errorBarCapShape=none;
referenceLine x=1;
endLayout; /* overlay */
endLayout; /* lattice */
endgraph;
end;
run;
ods _all_ close;
ods listing image_dpi=300;
ods graphics / reset width=6in height=4.5in imagename='SAS94M2_ForestPlot_GTL';
proc sgrender data=forest2 template=forestAxisTable;
dynamic _bandColor='cxf0f0f0' _headerColor='cxd0d0d0';
run;
ods _all_ close;
The height of your plot is too tall for the number of observations yoy have. I suggest you reduce your height o 2.6" in the BEGINGRAPH statement. Also, remove the OFFSETMIN=0 in the YAxisOpts. Program attached.
data forest;
input Id methods $3-20 Countpct $22-40 HR lci hci;
indentWt=1;
ObsId=_n_;
datalines;
1 Primary analysis 0.64 (0.43-0.95) 0.64 0.43 0.95
1 Grace period . . . .
2 60 days 0.70 (0.49-1.01) 0.7 0.49 1.01
2 90 days 1.06 (0.86-1.32) 1.06 0.86 1.32
1 Age groups . . . .
2 <=65 years 0.43 (0.19-0.93) 0.43 0.19 0.93
2 >65 years 0.69 (0.44-1.08) 0.69 0.44 1.08
1 Gender . . . .
2 Female 0.69 (0.43-1.10) 0.69 0.43 1.1
2 Male 0.57 (0.28-1.15) 0.57 0.28 1.15
1 CVD history . . . .
2 Without 0.69 (0.43-1.08) 0.69 0.43 1.08
2 With 0.50 (0.24-1.06) 0.5 0.24 1.06
;
run;
data forest2;
set forest;
/*if count ne . then CountPct=put(count, 4.0) || "(" || put(percent, 3.0) || ")";*/
if mod(obsId-1, 6) in (1, 2, 3) then ref=obsId;
/* Reduce indent for methods heading */
if id=1 then indentWt=0;
run;
/**
* Forest plot template using a one-column outermost lattice layout.
* The header uses a nested lattice layout in the top side bar.
* An overlay layout makes up the main area belci it.
* The left side table of values are axis tables placed inside
* inner margin of the overlay layout.
*/
proc template;
define statgraph forestAxisTable;
dynamic _bandColor _headerColor;
begingraph / designheight=2.6in;
discreteattrmap name='text' / trimleading=true;
value '1' / textAttrs=(size=7 weight=bold);
value '2' / textAttrs=(color=gray size=6 weight=normal);
enddiscreteattrmap;
discreteattrvar attrvar=txtDAV var=id attrmap='text';
layout lattice / columns=1;
/*--Column headers--*/
sidebar / align=top;
layout lattice / columns=2 rowweights=uniform columnweights=(0.35 0.65)
backgroundcolor=_headerColor opaque=true;
entry textAttrs=(size=8 weight=bold) hAlign=left "Methods"
hAlign=right " HR (95% CI)";
entry textAttrs=(size=8 weight=bold) "Hazard Ratio";
endLayout;
endsidebar;
/* Single cell with inner margins for left and right tables */
layout overlay / xAxisOpts=(display=(line ticks tickvalues)
tickValueAttrs=(size=6 weight=bold)
labelAttrs=(size=6 weight=bold)
linearOpts=(tickValuePriority=true
tickValueList=(0.0 0.5 1.0 1.5 2.0))
offsetMin=0.1
)
yAxisOpts=(reverse=true display=none) wallDisplay=none;
/* Left-side table */
innerMargin / align=left gutter=0.1in;
axisTable y=obsId value=Methods / textGroup=txtDAV
indentWeight=indentwt display=(values);
axisTable y=obsId value=CountPct / display=(values);
endInnerMargin;
/* Odds Ratio plot */
referenceLine y=ref / lineAttrs=(thickness=15 color=_bandColor);
scatterPlot y=obsId x=HR / markerAttrs=(symbol=squareFilled)
xErrorLower=LCI xErrorUpper=HCI errorBarCapShape=none;
referenceLine x=1;
endLayout; /* overlay */
endLayout; /* lattice */
endgraph;
end;
run;
ods _all_ close;
ods listing image_dpi=200;
ods graphics / reset imagename='SAS94M2_ForestPlot_GTL';
proc sgrender data=forest2 template=forestAxisTable;
dynamic _bandColor='cxf0f0f0' _headerColor='cxd0d0d0';
run;
ods _all_ close;
Hi JayL
Can you try adjusting the width and height in the following statement to see if it makes a difference?
ods graphics / reset width=6in height=4.5in
Hi Norman,
Thanks so much for your help.
I've tried the method you recommend, however, it only changes the overall size of the graph proporionally, with no changes in
the space between rows.
I am trying to find an option that can change the line space under axistable, but has not figured it out yet.
Jay
The height of your plot is too tall for the number of observations yoy have. I suggest you reduce your height o 2.6" in the BEGINGRAPH statement. Also, remove the OFFSETMIN=0 in the YAxisOpts. Program attached.
data forest;
input Id methods $3-20 Countpct $22-40 HR lci hci;
indentWt=1;
ObsId=_n_;
datalines;
1 Primary analysis 0.64 (0.43-0.95) 0.64 0.43 0.95
1 Grace period . . . .
2 60 days 0.70 (0.49-1.01) 0.7 0.49 1.01
2 90 days 1.06 (0.86-1.32) 1.06 0.86 1.32
1 Age groups . . . .
2 <=65 years 0.43 (0.19-0.93) 0.43 0.19 0.93
2 >65 years 0.69 (0.44-1.08) 0.69 0.44 1.08
1 Gender . . . .
2 Female 0.69 (0.43-1.10) 0.69 0.43 1.1
2 Male 0.57 (0.28-1.15) 0.57 0.28 1.15
1 CVD history . . . .
2 Without 0.69 (0.43-1.08) 0.69 0.43 1.08
2 With 0.50 (0.24-1.06) 0.5 0.24 1.06
;
run;
data forest2;
set forest;
/*if count ne . then CountPct=put(count, 4.0) || "(" || put(percent, 3.0) || ")";*/
if mod(obsId-1, 6) in (1, 2, 3) then ref=obsId;
/* Reduce indent for methods heading */
if id=1 then indentWt=0;
run;
/**
* Forest plot template using a one-column outermost lattice layout.
* The header uses a nested lattice layout in the top side bar.
* An overlay layout makes up the main area belci it.
* The left side table of values are axis tables placed inside
* inner margin of the overlay layout.
*/
proc template;
define statgraph forestAxisTable;
dynamic _bandColor _headerColor;
begingraph / designheight=2.6in;
discreteattrmap name='text' / trimleading=true;
value '1' / textAttrs=(size=7 weight=bold);
value '2' / textAttrs=(color=gray size=6 weight=normal);
enddiscreteattrmap;
discreteattrvar attrvar=txtDAV var=id attrmap='text';
layout lattice / columns=1;
/*--Column headers--*/
sidebar / align=top;
layout lattice / columns=2 rowweights=uniform columnweights=(0.35 0.65)
backgroundcolor=_headerColor opaque=true;
entry textAttrs=(size=8 weight=bold) hAlign=left "Methods"
hAlign=right " HR (95% CI)";
entry textAttrs=(size=8 weight=bold) "Hazard Ratio";
endLayout;
endsidebar;
/* Single cell with inner margins for left and right tables */
layout overlay / xAxisOpts=(display=(line ticks tickvalues)
tickValueAttrs=(size=6 weight=bold)
labelAttrs=(size=6 weight=bold)
linearOpts=(tickValuePriority=true
tickValueList=(0.0 0.5 1.0 1.5 2.0))
offsetMin=0.1
)
yAxisOpts=(reverse=true display=none) wallDisplay=none;
/* Left-side table */
innerMargin / align=left gutter=0.1in;
axisTable y=obsId value=Methods / textGroup=txtDAV
indentWeight=indentwt display=(values);
axisTable y=obsId value=CountPct / display=(values);
endInnerMargin;
/* Odds Ratio plot */
referenceLine y=ref / lineAttrs=(thickness=15 color=_bandColor);
scatterPlot y=obsId x=HR / markerAttrs=(symbol=squareFilled)
xErrorLower=LCI xErrorUpper=HCI errorBarCapShape=none;
referenceLine x=1;
endLayout; /* overlay */
endLayout; /* lattice */
endgraph;
end;
run;
ods _all_ close;
ods listing image_dpi=200;
ods graphics / reset imagename='SAS94M2_ForestPlot_GTL';
proc sgrender data=forest2 template=forestAxisTable;
dynamic _bandColor='cxf0f0f0' _headerColor='cxd0d0d0';
run;
ods _all_ close;
Hi Sanjay,
Thanks so much! This is really helpful, I've learned a lot.
best regards,
Jay
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.