Dear
I’m trying to run PROC LIFETEST
to generate a failure function. I would like the graph to display patients at risk at selected timepoints, and I’d also like to customize the label of the Y-axis. I’ve made several attempts, but none have worked. The code I’m currently using is as follows:
%ProvideSurvivalMacros
%let yOptions = label="Cumulative probability of xxxxxxxx" shortlabel="Failure"
linearopts=(viewmin=0 viewmax=1
tickvaluelist=(0 .2 .4 .6 .8 1.0));
%let xOptions = shortlabel=XNAME offsetmin=.05
linearopts=(viewmax=MAXTIME tickvaluelist=(0 52 104 156 200)
tickvaluefitpolicy=XTICKVALFITPOL);
%let METHOD = "";
%let TitleText0 = "";
%CompileSurvivalTemplates
proc lifetest data=dataset plots=(survival( failure atrisk=(0 52 104 156 200)));
time time_to_evet* flag_event (0) ;
strata type /test=none;
run;
When I run the debug using the command %put &=yOptions; %put &=xOptions;
, I can correctly see in the log the label I want to assign to the Y-axis and the timepoints for the X-axis. However, in the graph, the Y-axis label does not change. The patients at risk are shown at the correct timepoints, but without the label being applied.
Thank you for any help or advice
KR
Hello @ChiSAS25 and welcome to the SAS Support Communities!
The new y-axis label is not applied to the failure plot because that plot uses a different ODS template than the survival plot. This is mentioned in the footnote of the documentation page "Graph Templates, Macros, and Macro Variables":
The macros do not affect any graph that uses graph templates other than the two templates that are modified here. The macros do not affect ... the failure plot that uses the template Stat.Lifetest.Graphics.ProductLimitFailure.
So you don't need any of those SAS-supplied macros and macro variables which are designed for the survival plot. Instead, you can modify a copy of the ProductLimitFailure template manually:
Before you start modifying ODS templates, submit
ods path show;
and make sure that the "Current ODS PATH list" that is written to the log looks like this:
1. SASUSER.TEMPLAT(UPDATE) 2. SASHELP.TMPLMST(READ)
or like this:
1. WORK.TEMPLAT(UPDATE) 2. SASUSER.TEMPLAT(READ) 3. SASHELP.TMPLMST(READ)
In the first case, your modified ODS template will be written to the SASUSER.TEMPLAT item store, in the second case to the WORK.TEMPLAT item store. Either way and most importantly, the original template remains READ(-only).
I have copied the code of the ProductLimitFailure template from the template browser and just made the two small modifications highlighted below:
proc template; define statgraph Stat.Lifetest.Graphics.ProductLimitFailure; dynamic NStrata xName maxTime plotAtRisk plotCensored plotCL plotHW plotEP labelCL labelHW labelEP yMin xtickVals xtickValFitPol method StratumID classAtRisk plotTest GroupName Transparency rowWeights SecondTitle TestName pValue _byline_ _bytitle_ _byfootnote_; BeginGraph; if (NSTRATA=1) if (EXISTS(STRATUMID)) entrytitle METHOD " Failure Curve" " for " STRATUMID; else entrytitle METHOD " Failure Curve"; endif; if (PLOTATRISK=1) entrytitle "With Number of Subjects at Risk" / textattrs= GRAPHVALUETEXT; endif; layout overlay / xaxisopts=(shortlabel=XNAME offsetmin=.05 linearopts=(viewmax=MAXTIME tickvaluelist=XTICKVALS tickvaluefitpolicy=XTICKVALFITPOL)) yaxisopts=(label= "Failure Probability" shortlabel="Failure" linearopts=(viewmin= 0 viewmax=1 tickvaluelist=(0 .2 .4 .6 .8 1.0))); if (PLOTHW=1 AND PLOTEP=0) bandplot LimitUpper=eval (1-HW_LCL) LimitLower=eval ( 1-HW_UCL) x=TIME / displayTail=false modelname="Failure" fillattrs=GRAPHCONFIDENCE name="HW" legendlabel=LABELHW; endif; if (PLOTHW=0 AND PLOTEP=1) bandplot LimitUpper=eval (1-EP_LCL) LimitLower=eval ( 1-EP_UCL) x=TIME / displayTail=false modelname="Failure" fillattrs=GRAPHCONFIDENCE name="EP" legendlabel=LABELEP; endif; if (PLOTHW=1 AND PLOTEP=1) bandplot LimitUpper=eval (1-HW_LCL) LimitLower=eval ( 1-HW_UCL) x=TIME / displayTail=false modelname="Failure" fillattrs=GRAPHDATA1 datatransparency=.55 name="HW" legendlabel=LABELHW; bandplot LimitUpper=eval (1-EP_LCL) LimitLower=eval ( 1-EP_UCL) x=TIME / displayTail=false modelname="Failure" fillattrs=GRAPHDATA2 datatransparency=.55 name="EP" legendlabel=LABELEP; endif; if (PLOTCL=1) if (PLOTHW=1 OR PLOTEP=1) bandplot LimitUpper=eval (1-SDF_LCL) LimitLower=eval ( 1-SDF_UCL) x=TIME / displayTail=false modelname= "Failure" display=(outline) outlineattrs= GRAPHPREDICTIONLIMITS name="CL" legendlabel=LABELCL; else bandplot LimitUpper=eval (1-SDF_LCL) LimitLower=eval ( 1-SDF_UCL) x=TIME / displayTail=false modelname= "Failure" fillattrs=GRAPHCONFIDENCE name="CL" legendlabel=LABELCL; endif; endif; stepplot y=eval (1-SURVIVAL) x=TIME / name="Failure" rolename=( _tip1=ATRISK _tip2=EVENT) tiplabel=(y="Failure Probability" _tip1="Number at Risk" _tip2="Observed Events") tip=(x y _tip1 _tip2) legendlabel="Failure"; if (PLOTCENSORED) scatterplot y=eval (1-CENSORED) x=TIME / tiplabel=(y= "Failure Probability") 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=( topleft bottomright); endif; endif; if (PLOTATRISK=1) innermargin / align=bottom; axistable x=TATRISK value=ATRISK / display=(label) valueattrs=(size=7pt); endinnermargin; endif; endlayout; else entrytitle METHOD " Failure Curves"; if (EXISTS(SECONDTITLE)) entrytitle SECONDTITLE / textattrs=GRAPHVALUETEXT; endif; layout overlay / xaxisopts=(shortlabel=XNAME offsetmin=.05 linearopts=(viewmax=MAXTIME tickvaluelist=(0 52 104 156 200) tickvaluefitpolicy=XTICKVALFITPOL)) yaxisopts=(label= "Cumulative probability of xxxxxxxx" shortlabel="Failure" linearopts=(viewmin= 0 viewmax=1 tickvaluelist=(0 .2 .4 .6 .8 1.0))); if (PLOTHW=1) bandplot LimitUpper=eval (1-HW_LCL) LimitLower=eval ( 1-HW_UCL) x=TIME / displayTail=false group=STRATUM index= STRATUMNUM modelname="Failure" datatransparency= Transparency; endif; if (PLOTEP=1) bandplot LimitUpper=eval (1-EP_LCL) LimitLower=eval ( 1-EP_UCL) x=TIME / displayTail=false group=STRATUM index= STRATUMNUM modelname="Failure" datatransparency= Transparency; endif; if (PLOTCL=1) if (PLOTHW=1 OR PLOTEP=1) bandplot LimitUpper=eval (1-SDF_LCL) LimitLower=eval ( 1-SDF_UCL) x=TIME / displayTail=false group=STRATUM index=STRATUMNUM modelname="Failure" display=(outline) outlineattrs=(pattern=ShortDash); else bandplot LimitUpper=eval (1-SDF_UCL) LimitLower=eval ( 1-SDF_LCL) x=TIME / displayTail=false group=STRATUM index=STRATUMNUM modelname="Failure" datatransparency= Transparency; endif; endif; stepplot y=eval (1-SURVIVAL) x=TIME / group=STRATUM index= STRATUMNUM name="Failure" rolename=(_tip1=ATRISK _tip2=EVENT ) tiplabel=(y="Failure Probability" _tip1="Number at Risk" _tip2="Observed Events") tip=(x y _tip1 _tip2); if (PLOTCENSORED=1) scatterplot y=eval (1-CENSORED) x=TIME / tiplabel=(y= "Failure Probability") group=STRATUM index=STRATUMNUM markerattrs=(symbol=plus); endif; if (PLOTATRISK=1) innermargin / align=bottom; axistable x=TATRISK value=ATRISK / display=(label) valueattrs=(size=7pt) class=CLASSATRISK colorgroup= CLASSATRISK; endinnermargin; endif; DiscreteLegend "Failure" / title=GROUPNAME location=outside; if (PLOTCENSORED=1) if (PLOTTEST) layout gridded / rows=2 autoalign=(TOPLEFT BOTTOMRIGHT BOTTOM TOP) 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=(TOPLEFT BOTTOMRIGHT BOTTOM TOP) border=true BackgroundColor= GraphWalls:Color Opaque=true; entry "+ Censored"; endlayout; endif; else if (PLOTTEST=1) layout gridded / rows=1 autoalign=(TOPLEFT BOTTOMRIGHT BOTTOM TOP) 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; endif; if (_BYTITLE_) entrytitle _BYLINE_ / textattrs=GRAPHVALUETEXT; else if (_BYFOOTNOTE_) entryfootnote halign=left _BYLINE_; endif; endif; EndGraph; end; run;
(I assumed that your input dataset contains two or more strata because you use the STRATA statement. For graphs with only one stratum the highlighted changes would need to be made in the "if (NSTRATA=1) ..." block, not in the corresponding "else ..." block.)
Once the (SASUSER or WORK) template has been modified by submitting the above PROC TEMPLATE code, run your PROC LIFETEST step. Now the desired changes should take effect.
To revert to the original template, you can delete the modified one by running
proc template; delete Stat.Lifetest.Graphics.ProductLimitFailure / store=sasuser.templat; run;
or
proc template; delete Stat.Lifetest.Graphics.ProductLimitFailure / store=work.templat; run;
respectively.
I'm not familiar with those macros, but I see they are provided by SAS. I would probably start by running the code with the system option MPRINT turned on, then review the proc template code to look for the yaxisopts option and see if the label is there.
Alternatively, for custom graphs I often find it easier to have the PROC output the data, and then write my own SGPLOT or GTL to generate the graph.
Hello @ChiSAS25 and welcome to the SAS Support Communities!
The new y-axis label is not applied to the failure plot because that plot uses a different ODS template than the survival plot. This is mentioned in the footnote of the documentation page "Graph Templates, Macros, and Macro Variables":
The macros do not affect any graph that uses graph templates other than the two templates that are modified here. The macros do not affect ... the failure plot that uses the template Stat.Lifetest.Graphics.ProductLimitFailure.
So you don't need any of those SAS-supplied macros and macro variables which are designed for the survival plot. Instead, you can modify a copy of the ProductLimitFailure template manually:
Before you start modifying ODS templates, submit
ods path show;
and make sure that the "Current ODS PATH list" that is written to the log looks like this:
1. SASUSER.TEMPLAT(UPDATE) 2. SASHELP.TMPLMST(READ)
or like this:
1. WORK.TEMPLAT(UPDATE) 2. SASUSER.TEMPLAT(READ) 3. SASHELP.TMPLMST(READ)
In the first case, your modified ODS template will be written to the SASUSER.TEMPLAT item store, in the second case to the WORK.TEMPLAT item store. Either way and most importantly, the original template remains READ(-only).
I have copied the code of the ProductLimitFailure template from the template browser and just made the two small modifications highlighted below:
proc template; define statgraph Stat.Lifetest.Graphics.ProductLimitFailure; dynamic NStrata xName maxTime plotAtRisk plotCensored plotCL plotHW plotEP labelCL labelHW labelEP yMin xtickVals xtickValFitPol method StratumID classAtRisk plotTest GroupName Transparency rowWeights SecondTitle TestName pValue _byline_ _bytitle_ _byfootnote_; BeginGraph; if (NSTRATA=1) if (EXISTS(STRATUMID)) entrytitle METHOD " Failure Curve" " for " STRATUMID; else entrytitle METHOD " Failure Curve"; endif; if (PLOTATRISK=1) entrytitle "With Number of Subjects at Risk" / textattrs= GRAPHVALUETEXT; endif; layout overlay / xaxisopts=(shortlabel=XNAME offsetmin=.05 linearopts=(viewmax=MAXTIME tickvaluelist=XTICKVALS tickvaluefitpolicy=XTICKVALFITPOL)) yaxisopts=(label= "Failure Probability" shortlabel="Failure" linearopts=(viewmin= 0 viewmax=1 tickvaluelist=(0 .2 .4 .6 .8 1.0))); if (PLOTHW=1 AND PLOTEP=0) bandplot LimitUpper=eval (1-HW_LCL) LimitLower=eval ( 1-HW_UCL) x=TIME / displayTail=false modelname="Failure" fillattrs=GRAPHCONFIDENCE name="HW" legendlabel=LABELHW; endif; if (PLOTHW=0 AND PLOTEP=1) bandplot LimitUpper=eval (1-EP_LCL) LimitLower=eval ( 1-EP_UCL) x=TIME / displayTail=false modelname="Failure" fillattrs=GRAPHCONFIDENCE name="EP" legendlabel=LABELEP; endif; if (PLOTHW=1 AND PLOTEP=1) bandplot LimitUpper=eval (1-HW_LCL) LimitLower=eval ( 1-HW_UCL) x=TIME / displayTail=false modelname="Failure" fillattrs=GRAPHDATA1 datatransparency=.55 name="HW" legendlabel=LABELHW; bandplot LimitUpper=eval (1-EP_LCL) LimitLower=eval ( 1-EP_UCL) x=TIME / displayTail=false modelname="Failure" fillattrs=GRAPHDATA2 datatransparency=.55 name="EP" legendlabel=LABELEP; endif; if (PLOTCL=1) if (PLOTHW=1 OR PLOTEP=1) bandplot LimitUpper=eval (1-SDF_LCL) LimitLower=eval ( 1-SDF_UCL) x=TIME / displayTail=false modelname= "Failure" display=(outline) outlineattrs= GRAPHPREDICTIONLIMITS name="CL" legendlabel=LABELCL; else bandplot LimitUpper=eval (1-SDF_LCL) LimitLower=eval ( 1-SDF_UCL) x=TIME / displayTail=false modelname= "Failure" fillattrs=GRAPHCONFIDENCE name="CL" legendlabel=LABELCL; endif; endif; stepplot y=eval (1-SURVIVAL) x=TIME / name="Failure" rolename=( _tip1=ATRISK _tip2=EVENT) tiplabel=(y="Failure Probability" _tip1="Number at Risk" _tip2="Observed Events") tip=(x y _tip1 _tip2) legendlabel="Failure"; if (PLOTCENSORED) scatterplot y=eval (1-CENSORED) x=TIME / tiplabel=(y= "Failure Probability") 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=( topleft bottomright); endif; endif; if (PLOTATRISK=1) innermargin / align=bottom; axistable x=TATRISK value=ATRISK / display=(label) valueattrs=(size=7pt); endinnermargin; endif; endlayout; else entrytitle METHOD " Failure Curves"; if (EXISTS(SECONDTITLE)) entrytitle SECONDTITLE / textattrs=GRAPHVALUETEXT; endif; layout overlay / xaxisopts=(shortlabel=XNAME offsetmin=.05 linearopts=(viewmax=MAXTIME tickvaluelist=(0 52 104 156 200) tickvaluefitpolicy=XTICKVALFITPOL)) yaxisopts=(label= "Cumulative probability of xxxxxxxx" shortlabel="Failure" linearopts=(viewmin= 0 viewmax=1 tickvaluelist=(0 .2 .4 .6 .8 1.0))); if (PLOTHW=1) bandplot LimitUpper=eval (1-HW_LCL) LimitLower=eval ( 1-HW_UCL) x=TIME / displayTail=false group=STRATUM index= STRATUMNUM modelname="Failure" datatransparency= Transparency; endif; if (PLOTEP=1) bandplot LimitUpper=eval (1-EP_LCL) LimitLower=eval ( 1-EP_UCL) x=TIME / displayTail=false group=STRATUM index= STRATUMNUM modelname="Failure" datatransparency= Transparency; endif; if (PLOTCL=1) if (PLOTHW=1 OR PLOTEP=1) bandplot LimitUpper=eval (1-SDF_LCL) LimitLower=eval ( 1-SDF_UCL) x=TIME / displayTail=false group=STRATUM index=STRATUMNUM modelname="Failure" display=(outline) outlineattrs=(pattern=ShortDash); else bandplot LimitUpper=eval (1-SDF_UCL) LimitLower=eval ( 1-SDF_LCL) x=TIME / displayTail=false group=STRATUM index=STRATUMNUM modelname="Failure" datatransparency= Transparency; endif; endif; stepplot y=eval (1-SURVIVAL) x=TIME / group=STRATUM index= STRATUMNUM name="Failure" rolename=(_tip1=ATRISK _tip2=EVENT ) tiplabel=(y="Failure Probability" _tip1="Number at Risk" _tip2="Observed Events") tip=(x y _tip1 _tip2); if (PLOTCENSORED=1) scatterplot y=eval (1-CENSORED) x=TIME / tiplabel=(y= "Failure Probability") group=STRATUM index=STRATUMNUM markerattrs=(symbol=plus); endif; if (PLOTATRISK=1) innermargin / align=bottom; axistable x=TATRISK value=ATRISK / display=(label) valueattrs=(size=7pt) class=CLASSATRISK colorgroup= CLASSATRISK; endinnermargin; endif; DiscreteLegend "Failure" / title=GROUPNAME location=outside; if (PLOTCENSORED=1) if (PLOTTEST) layout gridded / rows=2 autoalign=(TOPLEFT BOTTOMRIGHT BOTTOM TOP) 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=(TOPLEFT BOTTOMRIGHT BOTTOM TOP) border=true BackgroundColor= GraphWalls:Color Opaque=true; entry "+ Censored"; endlayout; endif; else if (PLOTTEST=1) layout gridded / rows=1 autoalign=(TOPLEFT BOTTOMRIGHT BOTTOM TOP) 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; endif; if (_BYTITLE_) entrytitle _BYLINE_ / textattrs=GRAPHVALUETEXT; else if (_BYFOOTNOTE_) entryfootnote halign=left _BYLINE_; endif; endif; EndGraph; end; run;
(I assumed that your input dataset contains two or more strata because you use the STRATA statement. For graphs with only one stratum the highlighted changes would need to be made in the "if (NSTRATA=1) ..." block, not in the corresponding "else ..." block.)
Once the (SASUSER or WORK) template has been modified by submitting the above PROC TEMPLATE code, run your PROC LIFETEST step. Now the desired changes should take effect.
To revert to the original template, you can delete the modified one by running
proc template; delete Stat.Lifetest.Graphics.ProductLimitFailure / store=sasuser.templat; run;
or
proc template; delete Stat.Lifetest.Graphics.ProductLimitFailure / store=work.templat; run;
respectively.
Thank you very much for your clear and detailed explanation. Tomorrow morning I’ll test it in my SAS programming, but I’ve understood the reason why the SAS code wasn’t working. Have a nice time!
Dear,
I confirm that the proposed solution works and I’m getting the correct analysis. I have several failure functions to process; in many cases the graph is fully complete (graph1 attached) while in others some X-axis labels are not displayed (graph2 attached) I assume because some are very close together). Is there any solution for this?
thanks in advance
Kind regards
@ChiSAS25 wrote:
(...) in many cases the graph is fully complete (graph1 attached) while in others some X-axis labels are not displayed (graph2 attached) I assume because some are very close together).
It rather seems to me that some of the tick values are not displayed because they are not listed in the TICKVALUELIST= option. I think it would be best to display the tick values (at least or only) for those times when the numbers of subjects at risk are displayed. There are convenient options for this: ATRISKTICK and ATRISKTICKONLY. So specify one of them in your PROC LIFETEST statement, e.g.,
proc lifetest data=dataset plots=survival(failure atrisk(atrisktickonly)=(0 56 84 112 140 365 553));
and replace the custom tick value list in the PROC TEMPLATE code by the default specification XTICKVALS:
... layout overlay / xaxisopts=(shortlabel=XNAME offsetmin=.05 linearopts=(viewmax=MAXTIME tickvaluelist=XTICKVALS tickvaluefitpolicy=XTICKVALFITPOL)) ...
(otherwise, the ATRISKTICK or ATRISKTICKONLY option would be overridden).
have you tried "atrisktickonly" ?
https://documentation.sas.com/doc/en/statug/latest/statug_kaplan_sect009.htm
Good news: We've extended SAS Hackathon registration until Sept. 12, so you still have time to be part of our biggest event yet – our five-year anniversary!
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.
Ready to level-up your skills? Choose your own adventure.