Displaying Summary Statistics in the Survival Plot

/****************************************************************/
/*          S A S   S A M P L E   L I B R A R Y                 */
/*                                                              */
/*    NAME: LIFTSSS                                             */
/*   TITLE: Displaying Summary Statistics in the Survival Plot  */
/*          Using ODS Graphics.                                 */
/* PRODUCT: STAT                                                */
/*  SYSTEM: ALL                                                 */
/*    KEYS: survival plot, summary statistics                   */
/*   PROCS: LIFETEST                                            */
/*    DATA:                                                     */
/*                                                              */
/* SUPPORT: sasycs                      UPDATE: 03/11/2008      */
/*     REF: PROC LIFETEST documentation                         */
/*    MISC:                                                     */
/*                                                              */
/****************************************************************/

/*****************************************************************
 PROC LIFETEST passes a number of summary statistics as dynamic
 variables to the ODS Graphics for survival plots (see the Section
 "Additional Dynamic Variables for Survival Plots Using ODS
 Graphics" in the LIFETEST documentation). In this example, the
 graphical template (Stat.LIFETEST.Graphics.ProductLimitSurvival)
 for displaying the product-limit survival curves is modified
 to accomodate the display of the summary statistics. One can
 download Stat.Graphics.ProductLimitSurvival to the log by
 running PROC TEMPLATE as follows:

 proc template;
    source Stat.Lifetest.Graphics.ProductLimitSurvival;
 run;

 The lattice layout is used to display the survival curves in the
 plotting area and the summary statistics in the Columnheader area.
 Summary statistics are displayed using the macro
 %ProductLimitStat(n=,fmt=), where n= specifies the number of
 strata, and fmt= specifies a format for displaying the median
 survival and the corresponding confidence limits. By default,
 fmt=F6.1.

 A note of caution. After you run PROC TEMPLATE with the change,
 the new template is stored in your sasuser directory. If you
 want to restore to the original template, you can use PROC
 template as follows:

 proc template;
    delete  Stat.Lifetest.Graphics.ProductLimitSurvival;
 run;
 *****************************************************************/

%macro ProductLimitStat(n=,fmt=F6.1);
   dynamic PctMedianConfid;
   %if &n=1 %then %do;
      dynamic NObs NEvent Median LowerMedian UpperMedian;
   %end;
   %else %do;
      %do i=1 %to &n;
        dynamic StrVal&i NObs&i NEvent&i Median&i LowerMedian&i UpperMedian&i;;
      %end;
   %end;

   %if &n=1 %then %let ncol=4;
   %else          %let ncol=5;

   layout overlay / pad=(top=5);
      layout gridded / columns=&ncol border=TRUE;
         %if &n>1 %then entry " ";;
         entry "No. of Subjects";
         entry "Event";
         entry "Censored";
         entry "Median Survival (" PctMedianConfid " CL)";
         %do i=1 %to &n;
            %if &n=1 %then %do;
               entry NObs;
               entry NEvent;
               entry eval(NObs-NEvent);
               entry eval(put(Median,&fmt)) " ( "
                     eval(put(LowerMedian,&fmt)) " "
                     eval(put(UpperMedian,&fmt)) " )";
            %end;
            %else %do;
               entry halign=left StrVal&i;
               entry NObs&i;
               entry NEvent&i;
               entry eval(NObs&i-NEvent&i);
               entry eval(put(Median&i,&fmt)) " ( "
                     eval(put(LowerMedian&i,&fmt)) " "
                     eval(put(UpperMedian&i,&fmt)) " )";
            %end;
         %end;
      endlayout;
   endlayout;
%mend ProductLimitStat;


proc template ;

/* modified Stat.Lifetest.Graphics.ProductLimitSurvival template */
define statgraph Stat.Lifetest.Graphics.ProductLimitSurvival;
   dynamic NStrata xName plotAtRisk plotCensored plotCL plotHW plotEP labelCL
      labelHW labelEP maxTime StratumID classAtRisk plotBand plotTest
      GroupName yMin Transparency SecondTitle TestName pValue;
   BeginGraph;
      if (NSTRATA=1)
         if (EXISTS(STRATUMID))
            entrytitle "Product-Limit Survival Estimate" " for " STRATUMID;
         else
            entrytitle "Product-Limit Survival Estimate";
         endif;
         if (PLOTATRISK)
            entrytitle "with Number of Subjects at Risk" / textattrs=
            GRAPHVALUETEXT;
         endif;
         layout lattice / rows=1 columns=1;
         layout overlay / xaxisopts=(shortlabel=XNAME offsetmin=.05 linearopts=(
            viewmax=MAXTIME)) yaxisopts=(label="Survival Probability" shortlabel
              ="Survival" linearopts=(viewmin=0 viewmax=1 tickvaluelist=(0 .2 .4
               .6 .8 1.0)));
               if (PLOTHW=1 AND PLOTEP=0)
                bandplot LimitUpper=HW_UCL LimitLower=HW_LCL x=TIME / modelname=
                  "Survival" fillattrs=GRAPHCONFIDENCE name="HW" legendlabel=
                  LABELHW;
               endif;
               if (PLOTHW=0 AND PLOTEP=1)
                bandplot LimitUpper=EP_UCL LimitLower=EP_LCL x=TIME / modelname=
                  "Survival" fillattrs=GRAPHCONFIDENCE name="EP" legendlabel=
                  LABELEP;
               endif;
               if (PLOTHW=1 AND PLOTEP=1)
                bandplot LimitUpper=HW_UCL LimitLower=HW_LCL x=TIME / modelname=
                  "Survival" fillattrs=GRAPHDATA1 datatransparency=.55 name="HW"
                  legendlabel=LABELHW;
               bandplot LimitUpper=EP_UCL LimitLower=EP_LCL x=TIME / modelname=
                  "Survival" fillattrs=GRAPHDATA2 datatransparency=.55 name="EP"
                  legendlabel=LABELEP;
               endif;
               if (PLOTCL=1)
                  if (PLOTHW=1 OR PLOTEP=1)
               bandplot LimitUpper=SDF_UCL LimitLower=SDF_LCL x=TIME / modelname
               ="Survival" display=(outline) outlineattrs=GRAPHPREDICTIONLIMITS
                     name="CL" legendlabel=LABELCL;
                  else
               bandplot LimitUpper=SDF_UCL LimitLower=SDF_LCL x=TIME / modelname
                    ="Survival" fillattrs=GRAPHCONFIDENCE name="CL" legendlabel=
                     LABELCL;
                  endif;
               endif;
             stepplot y=SURVIVAL x=TIME / name="Survival" rolename=(_tip1=ATRISK
                 _tip2=EVENT) tip=(y x Time _tip1 _tip2) legendlabel="Survival";
               if (PLOTCENSORED=1)
                 scatterplot y=CENSORED x=TIME / markerattrs=(symbol=plus) name=
                  "Censored" legendlabel="Censored";
               endif;
               if (PLOTCL=1 OR PLOTHW=1 OR PLOTEP=1)
                  discretelegend "Censored" "CL" "HW" "EP" / location=outside
                  halign=center;
               else
                  if (PLOTCENSORED=1)
                 discretelegend "Censored" / location=inside autoalign=(topright
                     bottomleft);
                  endif;
               endif;
               if (PLOTATRISK=1)
                  innermargin / align=bottom;
                blockplot x=TATRISK block=ATRISK / repeatedvalues=true display=(
                     values) valuehalign=start valuefitpolicy=truncate
                     labelposition=left labelattrs=GRAPHVALUETEXT valueattrs=
                     GRAPHDATATEXT (size=7pt) includemissingclass=false;
                  endinnermargin;
               endif;
            endlayout;
            columnheaders;
               %ProductLimitStat(n=1);
            endcolumnheaders;
         endlayout;
      else
         entrytitle "Product-Limit Survival Estimates";
         if (EXISTS(SECONDTITLE))
            entrytitle SECONDTITLE / textattrs=GRAPHVALUETEXT;
         endif;
         layout lattice / rows=1 columns=1;
        layout overlay / xaxisopts=(shortlabel=XNAME offsetmin=.05 linearopts=(
            viewmax=MAXTIME)) yaxisopts=(label="Survival Probability" shortlabel
              ="Survival" linearopts=(viewmin=0 viewmax=1 tickvaluelist=(0 .2 .4
                .6 .8 1.0)));
               if (PLOTHW)
                  bandplot LimitUpper=HW_UCL LimitLower=HW_LCL x=TIME / group=
                 STRATUM index=STRATUMNUM modelname="Survival" datatransparency=
                  Transparency;
               endif;
               if (PLOTEP)
                  bandplot LimitUpper=EP_UCL LimitLower=EP_LCL x=TIME / group=
                 STRATUM index=STRATUMNUM modelname="Survival" datatransparency=
                  Transparency;
               endif;
               if (PLOTCL)
                  if (PLOTBAND)
                  bandplot LimitUpper=SDF_UCL LimitLower=SDF_LCL x=TIME / group=
                STRATUM index=STRATUMNUM modelname="Survival" display=(outline);
               else
                  bandplot LimitUpper=SDF_UCL LimitLower=SDF_LCL x=TIME / group=
                 STRATUM index=STRATUMNUM modelname="Survival" datatransparency=
                  Transparency;
               endif;
               endif;
               stepplot y=SURVIVAL x=TIME / group=STRATUM index=STRATUMNUM name=
                  "Survival" rolename=(_tip1=ATRISK _tip2=EVENT) tip=(y x Time
                  _tip1 _tip2);
               if (PLOTCENSORED)
                  scatterplot y=CENSORED x=TIME / group=STRATUM index=STRATUMNUM
                  markerattrs=(symbol=plus);
               endif;
               if (PLOTATRISK)
                  innermargin / align=bottom;
                  blockplot x=TATRISK block=ATRISK / class=CLASSATRISK
                    repeatedvalues=true display=(label values) valuehalign=start
                     valuefitpolicy=truncate labelposition=left labelattrs=
                     GRAPHVALUETEXT valueattrs=GRAPHDATATEXT (size=7pt)
                     includemissingclass=false;
                     endinnermargin;
               endif;
               DiscreteLegend "Survival" / title=GROUPNAME location=outside;
               if (PLOTCENSORED)
                  if (PLOTTEST)
               layout gridded / rows=2 autoalign=(TOPRIGHT BOTTOMLEFT TOP BOTTOM
                  ) border=true BackgroundColor=GraphWalls:Color Opaque=true;
                  entry "+ Censored";
                  if (PVALUE < .0001)
                     entry TESTNAME " p " eval (PUT(PVALUE, PVALUE6.4));
                  else
                     entry TESTNAME " p=" eval (PUT(PVALUE, PVALUE6.4));
                  endif;
                  endlayout;
                  else
               layout gridded / rows=1 autoalign=(TOPRIGHT BOTTOMLEFT TOP BOTTOM
                  ) border=true BackgroundColor=GraphWalls:Color Opaque=true;
                  entry "+ Censored";
                  endlayout;
               endif;
               else
                  if (PLOTTEST)
               layout gridded / rows=1 autoalign=(TOPRIGHT BOTTOMLEFT TOP BOTTOM
                  ) border=true BackgroundColor=GraphWalls:Color Opaque=true;
                  if (PVALUE < .0001)
                     entry TESTNAME " p " eval (PUT(PVALUE, PVALUE6.4));
                  else
                     entry TESTNAME " p=" eval (PUT(PVALUE, PVALUE6.4));
                  endif;
                  endlayout;
                  endif;
               endif;
            endlayout;
            columnheaders;
                if (NStrata=2) %ProductLimitStat(n=2);endif;
                if (NStrata=3) %ProductLimitStat(n=3);endif;
                if (NStrata=4) %ProductLimitStat(n=4);endif;
                if (NStrata=5) %ProductLimitStat(n=5);endif;
                if (NStrata=6) %ProductLimitStat(n=6);endif;
            endcolumnheaders;
         endlayout;
      endif;
   EndGraph;
end;

run;


proc format;
    value treat 0 = 'standard' 1 = 'test';

data prentice;
    input time state trt cell;
    casenum = _n_;
    label time = 'Survival Time'
          state = '1 = dead, 0 = censored';
    datalines;
 72 1 1 1
411 1 1 1
228 1 1 1
231 0 0 1
242 1 0 1
991 1 0 1
111 1 0 1
  1 1 0 1
587 1 0 1
389 1 0 1
 33 1 0 1
 25 1 0 1
357 1 0 1
467 1 0 1
201 1 0 1
  1 1 0 1
 30 1 0 1
 44 1 0 1
283 1 0 1
 15 1 0 1
 87 0 0 1
112 1 0 1
999 1 0 1
 11 1 1 1
 25 0 1 1
144 1 1 1
  8 1 1 1
 42 1 1 1
100 0 1 1
314 1 1 1
110 1 1 1
 82 1 1 1
 10 1 1 1
118 1 1 1
126 1 1 1
  8 1 1 2
 92 1 1 2
 35 1 1 2
117 1 1 2
132 1 1 2
 12 1 1 2
162 1 1 2
  3 1 1 2
 95 1 1 2
 24 1 0 2
 18 1 0 2
 83 0 0 2
 31 1 0 2
 51 1 0 2
 90 1 0 2
 52 1 0 2
 73 1 0 2
  8 1 0 2
 36 1 0 2
 48 1 0 2
  7 1 0 2
140 1 0 2
186 1 0 2
 84 1 0 2
 19 1 0 2
 45 1 0 2
 80 1 0 2
;
run;

ods graphics on;

ods select SurvivalPlot (persist);

proc lifetest data = prentice plots=s;
   time time*state(0);
   strata trt;
   format trt treat.;
run;


proc lifetest data = prentice plots=s(strata=individual);
   time time*state(0);
   strata trt;
   format trt treat.;
 run;

ods graphics off;

/* To restore the orginal template */
proc template;
   delete Stat.Lifetest.Graphics.ProductLimitSurvival;
   run;