Creating a Template That Is Easy to Modify

This example shows how you can modularize the entire template. The goal is to modularize the template and use macros such that simple changes, such as title changes, are no more complicated than the following:

%SurvivalTemplateRestore

%let TitleText0 = "Kaplan-Meier Plot";
%let TitleText1 = &titletext0 " for " STRATUMID;
%let TitleText2 = &titletext0;

%SurvivalTemplate

You can modularize the entire template as follows:

%macro SurvivalTemplateRestore;

   %global TitleText0 TitleText1 TitleText2 yOptions xOptions tips
           tipl groups bandopts gridopts blockopts censored censorstr;

   %let TitleText0 = METHOD " Survival Estimate";
   %let TitleText1 = &titletext0 " for " STRATUMID;
   %let TitleText2 = &titletext0 "s";         /* plural: Survival Estimates */

   %let yOptions   = label="Survival Probability"
                     shortlabel="Survival"
                     linearopts=(viewmin=0 viewmax=1
                                 tickvaluelist=(0 .2 .4 .6 .8 1.0));

   %let xOptions   = shortlabel=XNAME
                     offsetmin=.05
                     linearopts=(viewmax=MAXTIME tickvaluelist=XTICKVALS
                                 tickvaluefitpolicy=XTICKVALFITPOL);

   %let tips       = rolename=(_tip1= ATRISK _tip2=EVENT)
                     tiplabel=(_tip1="Number at Risk" _tip2="Observed Events")
                     tip=(x y _tip1 _tip2);
   %let tipl       = tiplabel=(y="Survival Probability");

   %let groups     = group=STRATUM index=STRATUMNUM;

   %let bandopts   = &groups modelname="Survival";

   %let gridopts   = autoalign=(TOPRIGHT BOTTOMLEFT TOP BOTTOM)
                     border=true BackgroundColor=GraphWalls:Color Opaque=true;

   %let blockopts  = repeatedvalues=true valuehalign=start
                     valuefitpolicy=truncate labelposition=left
                     labelattrs=GRAPHVALUETEXT includemissingclass=false
                     valueattrs=GRAPHDATATEXT(size=7pt);

   %let censored   = markerattrs=(symbol=plus);
   %let censorstr  = "+ Censored";
   %macro SurvivalTemplate;
      proc template;
         define statgraph Stat.Lifetest.Graphics.ProductLimitSurvival;
         dynamic NStrata xName plotAtRisk plotCL plotHW plotEP labelCL
            %if %nrbquote(&censored) ne %then plotCensored;
            labelHW labelEP maxTime xtickVals xtickValFitPol method StratumID
            classAtRisk plotBand plotTest GroupName yMin Transparency
            SecondTitle TestName pValue;
            BeginGraph;

               if (NSTRATA=1)
                  if (EXISTS(STRATUMID))
                     entrytitle &titletext1;
                  else
                     entrytitle &titletext0;
                  endif;
                  if (PLOTATRISK=1)
                     entrytitle "with Number of Subjects at Risk" / textattrs=
                        GRAPHVALUETEXT;
                  endif;

                  layout overlay / xaxisopts=(&xoptions) yaxisopts=(&yoptions);
                     %singlestratum
                  endlayout;

               else
                  entrytitle &titletext2;
                  if (EXISTS(SECONDTITLE))
                     entrytitle SECONDTITLE / textattrs=GRAPHVALUETEXT;
                  endif;

                  layout overlay / xaxisopts=(&xoptions) yaxisopts=(&yoptions);
                     %multiplestrata
                  endlayout;

               endif;

            EndGraph;
         end;
      run;
   %mend;
   %macro entry_p;
      if (PVALUE < .0001)
         entry TESTNAME " p " eval (PUT(PVALUE, PVALUE6.4));
      else
         entry TESTNAME " p=" eval (PUT(PVALUE, PVALUE6.4));
      endif;
   %mend;
   %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";

      if (PLOTCENSORED=1)
         scatterplot y=CENSORED x=TIME / &censored &tipl
            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 / display=(label values)
                                               &blockopts;
         endinnermargin;
      endif;
   %mend;

   %macro MultipleStrata;
     if (PLOTHW)
         bandplot LimitUpper=HW_UCL LimitLower=HW_LCL x=TIME / &bandopts
                  datatransparency=Transparency;
      endif;
      if (PLOTEP)
         bandplot LimitUpper=EP_UCL LimitLower=EP_LCL x=TIME / &bandopts
                  datatransparency=Transparency;
      endif;
      if (PLOTCL)
         if (PLOTBAND)
            bandplot LimitUpper=SDF_UCL LimitLower=SDF_LCL x=TIME / &bandopts
                     display=(outline);
         else
            bandplot LimitUpper=SDF_UCL LimitLower=SDF_LCL x=TIME / &bandopts
                     datatransparency=Transparency;
         endif;
      endif;
      stepplot y=SURVIVAL x=TIME / &groups name="Survival" &tips;

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

      if (PLOTATRISK=1)
         innermargin / align=bottom;
            blockplot x=TATRISK block=ATRISK / class=CLASSATRISK
               display=(label values) &blockopts;
         endinnermargin;
      endif;

      DiscreteLegend "Survival" / title=GROUPNAME location=outside;

      if (PLOTCENSORED)
         if (PLOTTEST)
            layout gridded / rows=2 &gridopts;
               entry &censorstr;
               %entry_p
            endlayout;
         else
            layout gridded / rows=1 &gridopts;
               entry &censorstr;
            endlayout;
         endif;
      else
         if (PLOTTEST)
            layout gridded / rows=1 &gridopts;
               %entry_p
            endlayout;
         endif;
      endif;
   %mend;

%SurvivalTemplate
%mend;

This modularized template is available in the SAS sample library. If you are using the SAS windowing environment, select Help $\blacktriangleright $ Getting Started with SAS Software. Select the Contents tab. Expand Learning to Use SAS, expand SAS Sample Programs, and expand SAS/STAT. Select Samples. Search for and select PROC LIFETEST Template.

The following changes were made to the template:

  • The outer macro, %SurvivalTemplateRestore, defines a set of macros and a set of global macro variables. This macro makes it easier to restore the default macros and macro variables. You should not use this outer macro to modify the templates. You should use it only to provide and restore all of the defaults.

  • Many options, including most of the options that are specified in multiple places in the template, are extracted to macro variables.

  • The main body of the template is in a macro, %SurvivalTemplate, so that it is easier to recompile the template after making changes.

  • The table for p-values is stored in the macro, %Entry_P.

  • The revised template for the single-stratum case is stored in the macro %SingleStratum.

  • The revised template for the multiple-stratum case is stored in the macro %MultipleStrata.

  • The template has been re-indented.

These changes make it easier to identify the relevant parts of the template, modify them, and recompile the template. All subsequent parts of this example modify this rewritten and more modular template.

Do not edit the template inside the %SurvivalTemplateRestore macro. Rather, copy the %LET statements and macro definitions and modify them outside the context of the %SurvivalTemplateRestore macro. If you work this way, then you can restore the defaults with a two step process:

  1. You can restore the default macros and macro variables by running the following step:

    %SurvivalTemplateRestore
    
  2. You can restore the default template by running the following step:

    proc template;
       delete Stat.Lifetest.Graphics.ProductLimitSurvival / store=sasuser.templat;
    run;
    

A simple, complete program, with set up, template modification, and clean up works as follows:

                                          /* Make the macros and macro      */
%SurvivalTemplateRestore                  /* variables available            */

%let TitleText0 = "Kaplan-Meier Plot";    /* Change the title.              */
%let TitleText1 = &titletext0 " for " STRATUMID;
%let TitleText2 = &titletext0;

%SurvivalTemplate                         /* Compile the template with      */
                                          /* the new title.                 */

proc lifetest data=sashelp.BMT            /* Perform the analysis and make  */
              plots=survival(cb=hw test); /* the graph.                     */
   time T * Status(0);
   strata Group;
run;

%SurvivalTemplateRestore                  /* Restore the default macros     */
                                          /* and macro variables.           */

proc template;                            /* Restore the default template.  */
   delete Stat.Lifetest.Graphics.ProductLimitSurvival / store=sasuser.templat;
run;

The results of this step are not shown, but this same template modification is discussed in the next section. The %SurvivalTemplateRestore macro creates the macros and macro variables that you can modify to change the template. The %SurvivalTemplate macro compiles the template with the macro variable changes and macro changes (if any were performed). By default, the compiled template is stored in the Sasuser.Templat item store. PROC LIFETEST makes the graph. The %SurvivalTemplateRestore macro restores the default macros and macro variables. The %SurvivalTemplateRestore macro ends by calling the %SurvivalTemplate macro, so it also compiles and stores the default template in the Sasuser.Templat item store. The PROC TEMPLATE step deletes the compiled template from the Sasuser.Templat item store so that the original template from the Sashelp.Tmplmst item store is used in subsequent runs.

Deleting the compiled template from the Sasuser.Templat item store does not change the macros or macro variables. Hence, if you do not restore the macros and macro variables, but you delete the compiled template, change a different macro variable, and recompile the template in the same SAS session, you will see the effects of both changes. In practice, you do not need to restore the default macros and macro variables when you are done unless (as is the case in this example) you go on in the same SAS session to make other template changes and you do not want your previous template changes to affect subsequent graphs.

If you modify and manipulate this template frequently, you might find it more convenient to modify the %SurvivalTemplateRestore macro along the following lines:

%macro SurvivalTemplateRestore(action);
   .
   .
   .
   %if       &action = compile %then %SurvivalTemplate;
   %else %if &action = delete  %then %do;
      proc template;
         delete Stat.Lifetest.Graphics.ProductLimitSurvival / store=sasuser.templat;
      run;
   %end;
%mend;

This modification enables you to clean up with a single step.