BookmarkSubscribeRSS Feed
☑ This topic is solved. Need further help from the community? Please sign in and ask a new question.
Anita_n
Pyrite | Level 9

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?

1 ACCEPTED SOLUTION

Accepted Solutions
Zard
SAS Employee

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_0-1696344727730.png

 

 

View solution in original post

7 REPLIES 7
Anita_n
Pyrite | Level 9

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;
Zard
SAS Employee

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_, '&amp;', '&');
  _infile_ = tranwrd(_infile_, '&lt;' , '<');
  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;

Zard_0-1696266407021.png

 


proc print data=Quartiles;
  where percent=50;
run;

proc print data=Summary;
run;

Zard_1-1696266495391.png

Zard_2-1696266505996.png

 

Anita_n
Pyrite | Level 9

Thanks for your reply, after capturing the Quartiles and the CensoredSummary. How do I get them into my plot using sgplot?

Zard
SAS Employee

This is the SGPLOT result so far:

Zard_0-1696269208149.png

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. 

Anita_n
Pyrite | Level 9

@Zard  Thanks, I just want to add this table  to this proc sgplot chart like in the proc lifetest

 

Anita_n_0-1696274611413.png

 

Anita_n_1-1696274696573.png

 

Zard
SAS Employee

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_0-1696344727730.png

 

 

Anita_n
Pyrite | Level 9

@Zard thankyou 

SAS Innovate 2025: Register Now

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!

What is ANOVA?

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.

Discussion stats
  • 7 replies
  • 2892 views
  • 0 likes
  • 2 in conversation