I have been trying to make a forest plot but I have been having trouble with the column that shows the mean value and the 95% confidence interval.
data forest;
input Id Subgroup $3-27 Mean Low High PValue $ mean_CI $;
zero=0; one=1;
pval_lbl='P value';
mean_CI='Mean change';
N='No.';
ObsId=_n_;
datalines;
1 GroupA .............. . . . . .
2 ..Univariate.............5.3 3.1 7.1 <0.01 5.3-(3.1-7.1)
1 GroupB ....... . . . . .
2 ..Univariate.............3.1 1.6 5.2 0.01 3.1-(1.6-5.2)
1 GroupC ............. . . . . .
2 ..Univariate.............0.2 -1.1 2.0 0.98 0.2-(-1.1-2.0)
;
run;
The actual forest plot with the dot corresponding to the effect size and the bars indicating the confidence interval all come out correctly.
The problem is the code for the variable mean_CI. I am trying to include the actual text for the mean value and the confidence interval next to the plot. I have coded this as a categorical variable so that it would allow me to include the parentheses. I have lumped together the mean and the 95% CI as with this example: 5.3-(3.1-7.1)
I put a dash after the mean hoping that this would allow the mean and the confidence interval to be grouped together in the text (I could then use photoshop to remove the extra dash).
However, when I run the code, it ends up showing that value as follows: 5.3-(3.1
So it removes the upper limit from the 95% confidence interval.
Does anyone have advice for how I can adjust my code so that I can group both the mean and the 95% confidence interval together?
Thanks!
It would help immensely if you shared the code.
EDIT: I can't tell if you're having issues with the graph being created or reading in the data or what you actually need help with here.
/*--CTSPedia General AE G1 Forest Plot--*/
%let gpath='C:\';
ods html close;
/*--To retain leading and trailing blanks, we must use nbsp instead of blank--*/
/*--For visibility, we have used '.' in place of blanks --*/
/*-- Later these '.' values are changed to nbsp 'A0'x --*/
/*--Regular leading blanks will be stripped, losing the indentation --*/
/*--Add "Id" to identify subgroup headings from values --*/
data forest;
input Id Subgroup $3-27 Mean Low High PValue $ mean_CI $;
zero=0; one=1;
pval_lbl='P value';
meanCI='Mean change';
ObsId=_n_;
datalines;
1 GroupA (n=50).............. . . . .
2 ..Univariate.............5.3 3.1 7.1 <0.01 5.3-(3.1-7.1)
1 GroupB (n=90)....... . . . . .
2 ..Univariate.............3.1 1.6 5.2 0.01 3.1-(1.6-5.2)
1 GroupC (n=35)............. . . . . .
2 ..Univariate.............0.2 -1.1 2.0 0.98 0.2-(-1.1-2.0)
;
run;
ods listing gpath='/myfolders/';
/*proc print;run;*/
/*--Replace '.' in subgroup with blank--*/
data forest2;
set forest;
subgroup=translate(subgroup, ' ', '.');
val=mod(_N_-1, 6);
if val eq 1 or val eq 2 or val eq 3 then ref=obsid;
/*--Separate Subgroup headers and obs into separate columns--*/
if id=1 then do;
heading=subgroup;
subgroup='';
end;
run;
/*proc print;run;*/
/*--Create font with smaller fonts for axis label, value and data--*/
proc template;
define style listingSF;
parent = Styles.Listing;
style GraphFonts from GraphFonts
"Fonts used in graph styles" /
'GraphDataFont' = ("<sans-serif>, <MTsans-serif>", 10pt)
'GraphValueFont' = ("<sans-serif>, <MTsans-serif>", 10pt)
'GraphLabelFont' = ("<sans-serif>, <MTsans-serif>", 10pt);
;
end;
run;
ods listing style=listingSF;
ods graphics on/ outputfmt=jpeg;
ods trace on;
ods listing gpath='/myfolders/' image_dpi=300;
/*--Define templage for Forest Plot--*/
/*--Template uses a Layout Lattice of 4 columns--*/
proc template;
define statgraph Forest;
dynamic _bandcolor _headercolor _subgroupcolor;
begingraph;
layout lattice / columns=4 columnweights=(0.2 0.25 0.45 0.1);
/*--First Subgroup column, shows only the Y2 axis --*/
/*--Use HighLow plot to place the heading and subgroup values as HighLabels--*/
/*--Indenting is done by making the 2nd highlow bar 1 unit long --*/
/*--Highlow bar itself has thickness=0 --*/
layout overlay / walldisplay=none
xaxisopts=(display=none linearopts=(viewmin=0 viewmax=20))
yaxisopts=(reverse=true display=none tickvalueattrs=(size=10));
referenceline y=ref / lineattrs=(thickness=15 color=_bandcolor);
highlowplot y=obsid low=zero high=zero / highlabel=heading lineattrs=(thickness=0)
labelattrs=(size=10 family='Arial');
highlowplot y=obsid low=zero high=one / highlabel=subgroup lineattrs=(thickness=0)
labelattrs=(size=10 family='Arial');
endlayout;
/*--Second column showing mean change--*/
layout overlay / x2axisopts=(display=(tickvalues) offsetmin=0 offsetmax=0)
yaxisopts=(reverse=true display=none) walldisplay=none ;
referenceline y=ref / lineattrs=(thickness=15 color=_bandcolor);
scatterplot y=obsid x=meanCI / markercharacter=mean_CI xaxis=x2
markercharacterattrs=(family='Arial' size=10);
endlayout;
/*--Third column showing hazards ratio graph--*/
layout overlay / xaxisopts=(label='Change relative to control'
linearopts=(tickvaluepriority=true
tickvaluelist=(-2 0 2 4 6 8)))
yaxisopts=(reverse=true display=none) walldisplay=none;
referenceline y=ref / lineattrs=(thickness=15 color=_bandcolor);
highlowplot y=obsid low=low high=high / lineattrs=(thickness=1 color=cx000000);
scatterplot y=obsid x=mean / markerattrs=(symbol=squarefilled size=10 color=cx000000);
referenceline x=0;
endlayout;
/*--Fourth column showing p-values--*/
layout overlay / x2axisopts=(display=(tickvalues) offsetmin=0.25 offsetmax=0.25)
yaxisopts=(reverse=true display=none) walldisplay=none;
referenceline y=ref / lineattrs=(thickness=15 color=_bandcolor);
scatterplot y=obsid x=pval_lbl / markercharacter=pvalue xaxis=x2
markercharacterattrs=(family='Arial' size=10);
endlayout;
endlayout;
entryfootnote halign=left textattrs=(size=10)
' ';
endgraph;
end;
run;
/*--Render Forest Plot without horizontal bands--*/
ods graphics / reset width=6in height=2in imagename='Forest_HighLow_93';
proc sgrender data=Forest2 template=Forest;
dynamic _bandcolor='white' _headercolor='white';
run;
ods graphics on/ outputfmt=jpeg;
ods trace on;
Below is what the plot looks like:
For the second column, I would like for the code to just read as follows:
5.3 (3.1-7.1)
3.1 (1.6-5.2)
0.2 (-1.1-2.0)
Any advice?
Thank you!
Clue 1: The displayed value is only showing 8 characters
Clue 2: The default length of an character variable read with and Input statement is 8 characters
Response: Read the original value with a specified length long enough to hold desired text such as
input Id Subgroup $3-27 Mean Low High PValue $ mean_CI :$15.;
You may need more than 15 depending on your actual values. You show at least one of the values that requires 14 characters,"0.2-(-1.1-2.0)", so that would be a minimum length.
I suggest following the newer CTSPedia graph example using AXISTABLE.
Try replacing this....it seems to work for me. There seems to be issues with the number of "." for Id=1 Subgroup B.
data forest;
input Id Subgroup $3-27 Mean Low High PValue $ mean_CI :$18.;
zero=0; one=1;
pval_lbl='P value';
meanCI='Mean change';
ObsId=_n_;
datalines;
1 GroupA (n=50).............. . . . .
2 ..Univariate.............5.3 3.1 7.1 <0.01 5.3-(3.1-7.1)
1 GroupB (n=90).............. . . . .
2 ..Univariate.............3.1 1.6 5.2 0.01 3.1-(1.6-5.2)
1 GroupC (n=35).............. . . . .
2 ..Univariate.............0.2 -1.1 2.0 0.98 0.2-(-1.1-2.0)
;
run;
Combining what @ballardw, @Reeza, and @twildone have already suggested, use this for the data step:
data forest;
input Id Subgroup $3-27 Mean Low High PValue $ mean_CI $18.;
infile datalines missover;
zero=0;
one=1;
pval_lbl='P value';
meanCI='Mean change';
ObsId=_n_;
datalines;
1 GroupA (n=50).............
2 ..Univariate.............5.3 3.1 7.1 <0.01 5.3 (3.1-7.1)
1 GroupB (n=90).............
2 ..Univariate.............3.1 1.6 5.2 0.01 3.1 (1.6-5.2)
1 GroupC (n=35).............
2 ..Univariate.............0.2 -1.1 2.0 0.98 0.2 (-1.1-2.0)
;
run;
And then you don't have to use Photoshop on the resulting graphic.
Registration is now open for SAS Innovate 2025 , our biggest and most exciting global event of the year! Join us in Orlando, FL, May 6-9.
Sign up by Dec. 31 to get the 2024 rate of just $495.
Register now!
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.