BookmarkSubscribeRSS Feed
SarahW13
Obsidian | Level 7

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!

7 REPLIES 7
Reeza
Super User

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. 

SarahW13
Obsidian | Level 7

/*--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:

 

SampleForestPlot.png

 

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!

ballardw
Super User

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.

Reeza
Super User
Echoing @ballardw, check your input data. I bet it hasn't been created properly in the first place. And I don't think you need the extra -, you can remove that if needed and just have the space.
Jay54
Meteorite | Level 14

I suggest following the newer CTSPedia graph example using AXISTABLE.

https://blogs.sas.com/content/graphicallyspeaking/2016/05/30/ctspedia-clinical-graphs-subgrouped-for...

 

twildone
Pyrite | Level 9

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;
SuzanneDorinski
Lapis Lazuli | Level 10

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.

 

Forest plot showing both mean and confidence intervalForest plot showing both mean and confidence interval

SAS Innovate 2025: Register Now

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!

How to Concatenate Values

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.

SAS Training: Just a Click Away

 Ready to level-up your skills? Choose your own adventure.

Browse our catalog!

Discussion stats
  • 7 replies
  • 2223 views
  • 6 likes
  • 6 in conversation