Customizing the Kaplan-Meier Survival Plot


The Larger Macros

The examples and information up to this point have illustrated how you can make simple changes to the survival plot. It is unlikely that you will ever have to do more than that. If you need to make changes to the overall layout of the graph, then you must modify one of the other macros. The %CompileSurvivalTemplates macro, which is the macro that compiles all the pieces that you modified, is as follows:

%macro CompileSurvivalTemplates;
   %local outside;
   proc template;
      %do outside = 0 %to 1;
         define statgraph
            Stat.Lifetest.Graphics.ProductLimitSurvival%scan(2,2-&outside);
            dynamic NStrata xName plotAtRisk
               %if %nrbquote(&censored) ne %then plotCensored;
               plotCL plotHW plotEP labelCL labelHW labelEP maxTime xtickVals
               xtickValFitPol rowWeights method StratumID classAtRisk
               plotTest GroupName Transparency SecondTitle TestName pValue
               _byline_ _bytitle_ _byfootnote_;
            BeginGraph %if %nrbquote(&graphopts) ne %then / &graphopts;;

            if (NSTRATA=1)
               %if &ntitles %then %do;
                  if (EXISTS(STRATUMID)) entrytitle &titletext1;
                  else                   entrytitle &titletext0;
                  endif;
               %end;

               %if &ntitles gt 1 %then %do;
                  %if not &outside %then if (PLOTATRISK=1);
                     entrytitle "With Number of Subjects at Risk" /
                                textattrs=GRAPHVALUETEXT;
                  %if not &outside %then %do; endif; %end;
               %end;

               %StmtsBeginGraph
               %AtRiskLatticeStart
               layout overlay / xaxisopts=(&xoptions) yaxisopts=(&yoptions);
                  %StmtsTop
                  %SingleStratum
                  %StmtsBottom
               endlayout;
               %AtRiskLatticeEnd

            else
               %if &ntitles %then %do; entrytitle &titletext2; %end;
               %if &ntitles gt 1 %then %do;
                  if (EXISTS(SECONDTITLE))
                     entrytitle SECONDTITLE / textattrs=GRAPHVALUETEXT;
                  endif;
               %end;

               %StmtsBeginGraph
               %AtRiskLatticeStart
               layout overlay / xaxisopts=(&xoptions) yaxisopts=(&yoptions);
                  %StmtsTop
                  %MultipleStrata
                  %StmtsBottom
               endlayout;
               %AtRiskLatticeEnd(class)

            endif;

            if (_BYTITLE_) entrytitle _BYLINE_ / textattrs=GRAPHVALUETEXT;
            else if (_BYFOOTNOTE_) entryfootnote halign=left _BYLINE_; endif;
            endif;
            EndGraph;
         end;
      %end;
   run;
%mend;

The macro %DO loop compiles the following two templates:

  • Stat.Lifetest.Graphics.ProductLimitSurvival when the macro variable Outside is 0 and %SCAN(2,2-&OUTSIDE) is null

  • Stat.Lifetest.Graphics.ProductLimitSurvival2 when the macro variable Outside is 1 and %SCAN(2,2-&OUTSIDE) is 2

The primary difference between these templates is that when the macro variable Outside is 1, a LAYOUT LATTICE statement block is used to place the at-risk table outside the graph. When Outside is 1, the macros %AtRiskLatticeStart and %AtRiskLatticeEnd provide the LAYOUT LATTICE statement block (two cells, plot above and at-risk table below) and the LAYOUT OVERLAY statement block for the at-risk table. The %AtRiskLatticeStart and %AtRiskLatticeEnd macros are defined as follows:

%macro AtRiskLatticeStart;
   %if &outside %then %do;
      layout lattice / rows=2 rowweights=ROWWEIGHTS
                       columndatarange=union rowgutter=10;
      cell;
   %end;
%mend;

%macro AtRiskLatticeEnd(useclassopts);
   %if &outside %then %do;
      endcell;
      cell;
         layout overlay / walldisplay=none xaxisopts=(display=none);
            axistable x=TATRISK value=ATRISK / &atriskopts
                      %if &useclassopts ne %then &classopts;;
         endlayout;
      endcell;
   endlayout;
   %end;
%mend;

The %CompileSurvivalTemplates macro relies on two other macros: %SingleStratum for the single-stratum case and %MultipleStrata for the multiple-strata case. The %SingleStratum macro is as follows:

%macro SingleStratum;
   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" &tips legendlabel="Survival"
            &stepopts;

   if (PLOTCENSORED=1)
      scatterplot y=CENSORED x=TIME / &censored &tiplabel
         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 not &outside %then %do;
      if (PLOTATRISK=1)
         innermargin / align=bottom;
            axistable x=TATRISK value=ATRISK / &atriskopts;
         endinnermargin;
      endif;
   %end;
%mend;

The %MultipleStrata macro is as follows:

%macro MultipleStrata;
  if (PLOTHW=1)
      bandplot LimitUpper=HW_UCL LimitLower=HW_LCL x=TIME / &bandopts
               datatransparency=Transparency;
   endif;
   if (PLOTEP=1)
      bandplot LimitUpper=EP_UCL LimitLower=EP_LCL x=TIME / &bandopts
               datatransparency=Transparency;
   endif;
   if (PLOTCL=1)
      if (PLOTHW=1 OR PLOTEP=1)
         bandplot LimitUpper=SDF_UCL LimitLower=SDF_LCL x=TIME / &bandopts
                  display=(outline) outlineattrs=(pattern=ShortDash);
      else
         bandplot LimitUpper=SDF_UCL LimitLower=SDF_LCL x=TIME / &bandopts
                  datatransparency=Transparency;
      endif;
   endif;

   stepplot y=SURVIVAL x=TIME / &groups name="Survival" &tips &stepopts;

   if (PLOTCENSORED=1)
      scatterplot y=CENSORED x=TIME / &groups &tiplabel &censored;
   endif;

   %if not &outside %then %do;
      if (PLOTATRISK=1)
         innermargin / align=bottom;
            axistable x=TATRISK value=ATRISK / &atriskopts &classopts;
         endinnermargin;
      endif;
   %end;

   %if %nrbquote(&legendopts) ne %then %do;
      DiscreteLegend "Survival" / &legendopts;
   %end;

   %if %nrbquote(&insetopts) ne %then %do;
      if (PLOTCENSORED=1)
         if (PLOTTEST=1)
            layout gridded / rows=2 &insetopts;
               entry &censorstr;
               %pValue
            endlayout;
         else
            layout gridded / rows=1 &insetopts;
               entry &censorstr;
            endlayout;
         endif;
      else
         if (PLOTTEST=1)
            layout gridded / rows=1 &insetopts;
               %pValue
            endlayout;
         endif;
      endif;
   %end;

%mend;