Dear all,
how do I add median survival, confidence interval to a Kaplan-Meier curve.
Also I will like to output the following: the number of censoring events, the number of events and the probabilities of events at predefined time points (e.g. 6 months, 12 months, 18 months, 24 months, 30 months)
Any help?
This PharmaSUG paper has a good example of adding summary statistics to a KM curve in PROC SGPLOT using TEXT statements. There is code for this plot:
Here is an example of what I want:
supposing I have this code , after creating the survival plot with proc life test, I will like to save the data and customise it using sgplot. I know all the necessary steps to do this, my only problem is I can get the table with the number of patients, event, censored mean survival and confidence interval to sgplot . Any help?
options mprint;
%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 Patients";
entry "Event";
entry "Censored";
entry "Median Survival (" PctMedianConfid " CI)";
%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;
source Stat.Lifetest.Graphics.ProductLimitSurvival;
define statgraph Stat.Lifetest.Graphics.ProductLimitSurvival;
dynamic NStrata xName plotAtRisk plotCensored plotCL plotHW plotEP labelCL labelHW labelEP
maxTime method StratumID classAtRisk plotBand plotTest GroupName yMin Transparency
SecondTitle TestName pValue;
mvar legtit; * Using this instead of GROUPNAME to add the title;
BeginGraph;
if (NSTRATA=1)
if (EXISTS(STRATUMID))
entrytitle "";
else
entrytitle "";
endif;
if (PLOTATRISK)
entrytitle "" / 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 "";
if (EXISTS(SECONDTITLE))
entrytitle "" / 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*/ legtit 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)
/* Removing log rank */
/*entry TESTNAME " p " eval (PUT(PVALUE, PVALUE6.4))*/
entry "";
else
/*entry TESTNAME " p=" eval (PUT(PVALUE, PVALUE6.4))*/
entry "";
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;
goptions reset = all;
ods graphics / reset = all;
*********************;
*Add this code before the lifetest.;
*********************;
*Add this code before the lifetest.;
data bmt;
set sashelp.bmt;
xval = 250;
yval = 0.2;
run;
goptions reset = all;
ods graphics / reset = all ;
ods trace on;
ods output Survivalplot =test;
proc lifetest data=bmt plots=survival(cb=hw atrisk=0 to 2500 by 500);
time T * Status(0);
strata Group /test=logrank adjust=sidak;
run;
ods trace off;
ods graphics off;
proc sgplot data=test nowall noautolegend noborder;
styleattrs axisextent=data;
step x=time y=survival / lineattrs=(pattern=solid color=darkcyan) name='s';
scatter x=time y=censored / markerattrs=(symbol=circlefilled size=6 color=darkcyan) name='c';
scatter x=time y=censored / markerattrs=(symbol=circlefilled size=4 color=darkcyan) group=stratum;
xaxistable atrisk / x=tatrisk location=inside valueattrs=(size=7pt color=red weight=bold) labelattrs=(size=6pt color=red) ;
xaxis label= "Time(in months)" minor labelattrs=(size=6pt family=arial color=dimgray) valueattrs=(size=6pt family=arial color=dimgray)
minorgrid grid;
yaxis label="survival probability" labelattrs=(size=6pt family=arial color=dimgray) valueattrs=(size=5pt family=arial color=dimgray) ;
keylegend 'c' / location=inside position=topright valueattrs=(color=dimgray size=5pt) noborder;
run;
Depending on what you want, you might be able to do the customizations directly in PROC LIFETEST, using the macros.
But you can save the Quartiles and the CensoredSummary ODS output data sets in order to capture the data in the added table. I used the %SurvivalSummaryTable to add the Number of Patients, etc. in the example below:
ods path(prepend) work.templat(update);
data _null_;
%let url = //support.sas.com/documentation/onlinedoc/stat/ex_code/151;
infile "http:&url/templft.html" device=url;
file 'macros.tmp';
retain pre 0;
input;
_infile_ = tranwrd(_infile_, '&', '&');
_infile_ = tranwrd(_infile_, '<' , '<');
if index(_infile_, '</pre>') then pre = 0;
if pre then put _infile_;
if index(_infile_, '<pre>') then pre = 1;
run;
%inc 'macros.tmp' / nosource;
%ProvideSurvivalMacros
%SurvivalSummaryTable
%CompileSurvivalTemplates
data bmt;
set sashelp.bmt;
xval = 250;
yval = 0.2;
run;
ods select SurvivalPlot;
proc lifetest data=bmt plots=(survival(cb=hw atrisk=0 to 2500 by 500));
time T*status(0);
strata group /test=logrank adjust=sidak;
ods output Quartiles=Quartiles CensoredSummary=Summary;
run;
proc print data=Quartiles;
where percent=50;
run;
proc print data=Summary;
run;
Thanks for your reply, after capturing the Quartiles and the CensoredSummary. How do I get them into my plot using sgplot?
This is the SGPLOT result so far:
And the LIFETEST plot already has the medians and censored summary. I'm thinking the LIFETEST KM plot is closer to being done than the SGPLOT KM plot, yes? If you can tell me what you want excluded from the LIFETEST plot, and exactly what you want added and where, I might be able to provide code.
This PharmaSUG paper has a good example of adding summary statistics to a KM curve in PROC SGPLOT using TEXT statements. There is code for this plot:
@Zard thankyou
Registration is now open for SAS Innovate 2025 , our biggest and most exciting global event of the year! Join us in Orlando, FL, May 6-9.
Sign up by Dec. 31 to get the 2024 rate of just $495.
Register now!
ANOVA, or Analysis Of Variance, is used to compare the averages or means of two or more populations to better understand how they differ. Watch this tutorial for more.
Find more tutorials on the SAS Users YouTube channel.