Sunday, December 15, 2013

SAS codes for creating the forest plot for sub-group analyses

This is from the SAS blog by Sanjay Matange.

/*--SAS 9.3 release or later                                             --*/
/*--Name:  Sanjay Matange                                                --*/
/*--Company:  SAS Institute Inc.                                         --*/
/*--Date   :  06Aug 2012                                                 --*/
/*--History:  06Aug2012/SNM - Add horizontal bands.                      --*/
/*--          06Sep2012/SNM - Full Width horizontal bands.               --*/
/*--          07Jan2013/SNM - Use HighLow plot for subgroup with fonts   --*/
/*-------------------------------------------------------------------------*/

/*--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 Count Percent Mean  Low  High  PCIGroup Group PValue;
  zero=0; one=1;
  PCI_lbl='PCI Group';
  grp_lbl='Group';
  pval_lbl='P Value';
  ObsId=_n_; 
  if count ne . then CountPct=put(count, 4.0) || "(" || put(percent, 3.0) || ")";
  datalines;
1 Overall..................2166  100  1.3   0.9   1.5  17.2  15.6  .
1 Age.......................     .    .     .     .    .     .     0.05
2 ..<= 65 Yr...............1534   71  1.5   1.05  1.9  17.0  13.2   .
2 ..> 65 Yr................ 632   29  0.8   0.6   1.25 17.8  21.3   .
1 Sex.......................     .    .     .     .    .     .     0.13
2 ..Male...................1690   78  1.5   1.05  1.9  16.8  13.5   .
2 ..Female................. 476   22  0.8   0.6   1.3  18.3  22.9   . 
1 Race or ethnic group......     .    .     .     .    .     .     0.52
2 ..Nonwhite............... 428   20  1.05  0.6   1.8  18.8  17.8   .
2 ..White..................1738   80  1.2   0.85  1.6  16.7  15.0   . 
1 From MI to Randomization..     .    .     .     .    .     .     0.81
2 ..<= 7 days.............. 963   44  1.2   0.8   1.5  18.9  18.6   .
2 ..> 7 days...............1203   56  1.15  0.75  1.5  15.9  12.9   .
1 Infract-related artery....     .    .     .     .    .     .     0.38
2 ..LAD.................... 781   36  1.4   0.9   1.9  20.1  16.2   .
2 ..Other..................1385   64  1.1   0.8   1.4  15.6  15.3   . 
1 Ejection Fraction.........     .    .     .     .    .     .     0.48
2 ..< 50%..................1151   54  1.2   0.8   1.5  22.6  20.4   .
2 ..>= 50%................. 999   46  0.9   0.6   1.4  10.7  11.1   . 
1 Diabetes..................     .    .     .     .    .     .     0.41
2 ..Yes.................... 446   21  1.4   0.9   2.0  29.3  23.3   .
2 ..No.....................1720   79  1.1   0.8   1.5  14.4  13.5   . 
1 Killip class..............     .    .     .     .    .     .     0.39
2 ..I......................1740   81  1.2   0.8   1.6  15.2  13.1   .
2 ..II-IV.................. 413   19  0.95  0.6   1.5  25.3  26.9   . 
;
run;
ods listing;
/*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>",7pt)                                
         'GraphValueFont' = ("<sans-serif>, <MTsans-serif>",7pt)
         'GraphLabelFont' = ("<sans-serif>, <MTsans-serif>",7pt, bold); 
; 
   end;
run;

%let dpi=150;
ods listing style=listingSF gpath=&gpath image_dpi=&dpi;

/*--Define templage for Forest Plot--*/
/*--Template uses a Layout Lattice of 5 columns--*/
proc template;
  define statgraph Forest;
  dynamic  _bandcolor _headercolor _subgroupcolor;
    begingraph;
      layout lattice / columns=4 columnweights=(0.23 0.07 0.4 0.3);

 /*--Column headers--*/
        sidebar / align=top;
       layout lattice / rows=2 columns=4 columnweights=(0.18 0.17 0.35 0.3)
            backgroundcolor=_headercolor opaque=true;
            entry textattrs=(size=8 weight=bold) halign=left "Subgroup";
   entry textattrs=(size=8 weight=bold) " No.of Patients (%)";
            entry textattrs=(size=8 weight=bold) "Hazard Ratio";
            entry halign=center textattrs=(size=8 weight=bold) "4-Yr Cumulative Event Rate" ;
      entry " "; 
            entry " "; 
   entry " "; 
            entry halign=center textattrs=(size=6) "Medical Therapy";
    endlayout;
        endsidebar;

 /*--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=(weight=bold));
   referenceline y=ref / lineattrs=(thickness=15 color=_bandcolor);
   highlowplot y=obsid low=zero high=zero / highlabel=heading lineattrs=(thickness=0)
               labelattrs=(size=7 weight=bold);
   highlowplot y=obsid low=zero high=one / highlabel=subgroup lineattrs=(thickness=0);
 endlayout;

 /*--Second column showing Count and percent--*/
 layout overlay / xaxisopts=(display=none) 
               yaxisopts=(reverse=true display=none) walldisplay=none;
    referenceline y=ref / lineattrs=(thickness=15 color=_bandcolor);
   scatterplot y=obsid x=zero / markercharacter=countpct  
               markercharacterattrs=graphvaluetext;
  endlayout;

 /*--Third column showing odds ratio graph--*/
 layout overlay / xaxisopts=(label='   <---PCI Better----     ----Medical Therapy Better--->'  
                         linearopts=(tickvaluepriority=true 
                            tickvaluelist=(0.0 0.5 1.0 1.5 2.0 2.5)))
               yaxisopts=(reverse=true display=none) walldisplay=none;
    referenceline y=ref / lineattrs=(thickness=15 color=_bandcolor);
   highlowplot y=obsid low=low high=high; 
          scatterplot y=obsid x=mean / markerattrs=(symbol=squarefilled);
    referenceline x=1;
 endlayout;

 /*--Fourth column showing PCIGroup and Group columns--*/
 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=pci_lbl / markercharacter=PCIGroup xaxis=x2  
               markercharacterattrs=graphvaluetext;
   scatterplot y=obsid x=grp_lbl / markercharacter=group xaxis=x2  
               markercharacterattrs=graphvaluetext;
          scatterplot y=obsid x=pval_lbl / markercharacter=pvalue xaxis=x2  
               markercharacterattrs=graphvaluetext;
  endlayout;
   endlayout;
 entryfootnote halign=left textattrs=(size=7) 
           'The p-value is from the test statistic for testing the interaction between the '
           'treatment and any subgroup variable';
 endgraph;
  end;
run;

/*--Render Forest Plot without horizontal bands--*/
ods graphics / reset width=7in height=5in imagename='Forest_HighLow_93';
proc sgrender data=Forest2 template=Forest;
dynamic _bandcolor='white' _headercolor='white';
run;

/*--Render Forest Plot with horizontal bands--*/
ods graphics / reset width=7in height=5in imagename='Forest_HighLow_Band_93';
proc sgrender data=Forest2 template=Forest;
dynamic _bandcolor='cxf0f0f0' _headercolor='cxd0d0d0';
run;

No comments:

Post a Comment