BookmarkSubscribeRSS Feed
☑ This topic is solved. Need further help from the community? Please sign in and ask a new question.
morenayan
Fluorite | Level 6
proc lifetest data=sascomp.adqs method=PL plots=(survival(atrisk cl) logsurv) alpha=0.05;
   time RETIME*STATUS(0);  
   strata Group;          
run;

This is my PROC LIFETEST program, which can output a survival plot for me.

I've already know if I add survival(test), then I can add log rank's P-value on my plot, which is:

proc lifetest data=sascomp.adqs method=PL plots=(survival(test atrisk cl) logsurv) alpha=0.05;
   time RETIME*STATUS(0);  
   strata Group;          
run;

But I don't know how to add median survival time and HR from Cox. Please help me, many thanks!

1 ACCEPTED SOLUTION

Accepted Solutions
FreelanceReinh
Jade | Level 19

Hello @morenayan,

 

I see two possible approaches: One was suggested in the 2023 post Re: Kaplan Meier using proc lifetest and involves reproducing the Kaplan-Meier plot with PROC SGPLOT and using TEXT statements. The second approach might be simpler because it just modifies the existing Kaplan-Meier plot (template), which is relatively easy thanks to the comprehensive macros that SAS has provided for this purpose (see Controlling the Survival Plot by Modifying Graph Templates). Below I provide an example using the SAS-supplied dataset SASHELP.BMT, based on the example Adding a Small Inset Table with Event Information from the documentation.

 

/* Create sample data for demonstration */

data have;
set sashelp.bmt(where=(group ne 'ALL'));
run;

/* Compute statistics */

ods select none;

ods output homtests=ht quartiles=qt;
proc lifetest data=have;
time t*status(0);
strata group;
run;

ods output parameterestimates=est;
proc phreg data=have;
class group;
model t*status(0)=group / rl;
run;

ods select all;

/* Combine and format statistics */

data stats;
merge qt(where=(percent=50) drop=t:)
      est(keep=ClassVal0 h: rename=(ClassVal0=Group));
by group;
length hr $20;
if hazardratio=. then hr='Ref';
else hr=put(HazardRatio,best4.)||' ('||put(HRLowerCL,best4.)||'-'||put(HRUpperCL,best4.)||')';
run;

proc format;
value med
.='NE'
other=[4.];
run;

/* Write statistics to macro variables */

proc sql noprint;
select probchisq into :pval from ht where test like 'L%';
select group, estimate format=med., lowerlimit format=med., upperlimit format=med., hr
  into :grp1-, :med1-, :lcl1-, :ucl1-, :hr1- from stats;
quit;

/* Modify inset of Kaplan-Meier plot to contain statistics */

%ProvideSurvivalMacros /* from https://support.sas.com/documentation/onlinedoc/stat/ex_code/151/templft.html */

%let InsetOpts  = ;
%let LegendOpts = title="+ Censored" location=inside autoalign=(Bottom);

%macro StmtsBottom;
%let t = / textattrs=(weight=bold);
layout gridded / columns=3 border=TRUE autoalign=(TopRight);
  entry halign=left "Logrank  p=&pval"; entry " "; entry " ";
  entry " "; entry " "; entry " ";
  entry halign=left "Group" &t; entry "Median (95% CI)" &t; entry "HR (95% CI)" &t;
  entry halign=left "&grp1"; entry "&med1 (&lcl1-&ucl1)"; entry "&hr1";
  entry halign=left "&grp2"; entry "&med2 (&lcl2-&ucl2)"; entry "&hr2";
endlayout;
%mend;

%CompileSurvivalTemplates

/* Create the modified Kaplan-Meier plot */

ods graphics on;
proc lifetest data=have plots=(survival(test atrisk cl) logsurv);
time t*status(0);
strata group;
run;

Result:

KM_Plot_with_statistics.png

 

View solution in original post

6 REPLIES 6
FreelanceReinh
Jade | Level 19

Hello @morenayan,

 

I see two possible approaches: One was suggested in the 2023 post Re: Kaplan Meier using proc lifetest and involves reproducing the Kaplan-Meier plot with PROC SGPLOT and using TEXT statements. The second approach might be simpler because it just modifies the existing Kaplan-Meier plot (template), which is relatively easy thanks to the comprehensive macros that SAS has provided for this purpose (see Controlling the Survival Plot by Modifying Graph Templates). Below I provide an example using the SAS-supplied dataset SASHELP.BMT, based on the example Adding a Small Inset Table with Event Information from the documentation.

 

/* Create sample data for demonstration */

data have;
set sashelp.bmt(where=(group ne 'ALL'));
run;

/* Compute statistics */

ods select none;

ods output homtests=ht quartiles=qt;
proc lifetest data=have;
time t*status(0);
strata group;
run;

ods output parameterestimates=est;
proc phreg data=have;
class group;
model t*status(0)=group / rl;
run;

ods select all;

/* Combine and format statistics */

data stats;
merge qt(where=(percent=50) drop=t:)
      est(keep=ClassVal0 h: rename=(ClassVal0=Group));
by group;
length hr $20;
if hazardratio=. then hr='Ref';
else hr=put(HazardRatio,best4.)||' ('||put(HRLowerCL,best4.)||'-'||put(HRUpperCL,best4.)||')';
run;

proc format;
value med
.='NE'
other=[4.];
run;

/* Write statistics to macro variables */

proc sql noprint;
select probchisq into :pval from ht where test like 'L%';
select group, estimate format=med., lowerlimit format=med., upperlimit format=med., hr
  into :grp1-, :med1-, :lcl1-, :ucl1-, :hr1- from stats;
quit;

/* Modify inset of Kaplan-Meier plot to contain statistics */

%ProvideSurvivalMacros /* from https://support.sas.com/documentation/onlinedoc/stat/ex_code/151/templft.html */

%let InsetOpts  = ;
%let LegendOpts = title="+ Censored" location=inside autoalign=(Bottom);

%macro StmtsBottom;
%let t = / textattrs=(weight=bold);
layout gridded / columns=3 border=TRUE autoalign=(TopRight);
  entry halign=left "Logrank  p=&pval"; entry " "; entry " ";
  entry " "; entry " "; entry " ";
  entry halign=left "Group" &t; entry "Median (95% CI)" &t; entry "HR (95% CI)" &t;
  entry halign=left "&grp1"; entry "&med1 (&lcl1-&ucl1)"; entry "&hr1";
  entry halign=left "&grp2"; entry "&med2 (&lcl2-&ucl2)"; entry "&hr2";
endlayout;
%mend;

%CompileSurvivalTemplates

/* Create the modified Kaplan-Meier plot */

ods graphics on;
proc lifetest data=have plots=(survival(test atrisk cl) logsurv);
time t*status(0);
strata group;
run;

Result:

KM_Plot_with_statistics.png

 

morenayan
Fluorite | Level 6

HI, thanks!!!

i've already solved my problem perfectly by using the second solutions you've provived.

SASuserlot
Barite | Level 11

Hi @FreelanceReinh  thank you for the solution on this post. I have a question on this . Is there any way I can move the legend inside for groups, like in the image below? I was able to get this graph generated from %newsurvmacro from @JeffMeyers  (https://communities.sas.com/t5/SAS-Communities-Library/Kaplan-Meier-Survival-Plotting-Macro-NEWSURV/...).  However, I am trying to lift the code from the above macro and incorporate in your code. Is it possible?

 
 
 

Screenshot 2026-02-21 163807.png

JeffMeyers
Barite | Level 11
There are two ways to incorporate the legend if you are using the gridded layout. The more manual way is to add legenditem statements and then make a discretelegend using the appropriate legend item name for that row. The second is to do a discretelegend with exclude statements to remove the items you don't want in the row, set the label to be white and size 1 so you just get the legend part. The second way is how I did it in the macro I think. A discretelegend is just another object that can go in the gridded layout cells like entry statements.
Ksharp
Super User

Here you go:

 

proc format;
   value grpLabel 1='ALL' 2='AML low risk' 3='AML high risk';
run;

data BMT;
        input DIAGNOSIS Ftime Status Gender@@;
        label Ftime="Days";
        format Diagnosis grpLabel.;
datalines;
1       2081       0       1       1       1602    0       1
1       1496       0       1       1       1462    0       0
1       1433       0       1       1       1377    0       1
1       1330       0       1       1       996     0       1
1       226        0       0       1       1199    0       1
1       1111       0       1       1       530     0       1
1       1182       0       0       1       1167    0       0
1       418        2       1       1       383     1       1
1       276        2       0       1       104     1       1
1       609        1       1       1       172     2       0
1       487        2       1       1       662     1       1
1       194        2       0       1       230     1       0
1       526        2       1       1       122     2       1
1       129        1       0       1       74      1       1
1       122        1       0       1       86      2       1
1       466        2       1       1       192     1       1
1       109        1       1       1       55      1       0
1       1          2       1       1       107     2       1
1       110        1       0       1       332     2       1
2       2569       0       1       2       2506    0       1
2       2409       0       1       2       2218    0       1
2       1857       0       0       2       1829    0       1
2       1562       0       1       2       1470    0       1
2       1363       0       1       2       1030    0       0
2       860        0       0       2       1258    0       0
2       2246       0       0       2       1870    0       0
2       1799       0       1       2       1709    0       0
2       1674       0       1       2       1568    0       1
2       1527       0       0       2       1324    0       1
2       957        0       1       2       932     0       0
2       847        0       1       2       848     0       1
2       1850       0       0       2       1843    0       0
2       1535       0       0       2       1447    0       0
2       1384       0       0       2       414     2       1
2       2204       2       0       2       1063    2       1
2       481        2       1       2       105     2       1
2       641        2       1       2       390     2       1
2       288        2       1       2       421     1       1
2       79         2       0       2       748     1       1
2       486        1       0       2       48      2       0
2       272        1       0       2       1074    2       1
2       381        1       0       2       10      2       1
2       53         2       0       2       80      2       0
2       35         2       0       2       248     1       1
2       704        2       0       2       211     1       1
2       219        1       1       2       606     1       1
3       2640       0       1       3       2430    0       1
3       2252       0       1       3       2140    0       1
3       2133       0       0       3       1238    0       1
3       1631       0       1       3       2024    0       0
3       1345       0       1       3       1136    0       1
3       845        0       0       3       422     1       0
3       162        2       1       3       84      1       0
3       100        1       1       3       2       2       1
3       47         1       1       3       242     1       1
3       456        1       1       3       268     1       0
3       318        2       0       3       32      1       1
3       467        1       0       3       47      1       1
3       390        1       1       3       183     2       0
3       105        2       1       3       115     1       0
3       164        2       0       3       93      1       0
3       120        1       0       3       80      2       1
3       677        2       1       3       64      1       0
3       168        2       0       3       74      2       0
3       16         2       0       3       157     1       0
3       625        1       0       3       48      1       0
3       273        1       1       3       63      2       1
3       76         1       1       3       113     1       0
3       363        2       1
;
run;


ods select none;
ods output SurvivalPlot=SurvivalPlot;
proc lifetest data=bmt plots=(survival);
time ftime*Status(0);
strata diagnosis;
run;
ods select all;








data legend;
input x y treat & $20. event $ median & $40.  hr & $20.;
label treat='DIAGNOSIS' event='Events/Total' median='Median(95%CI)' hr='HR(95%CI)';
cards;
0 4 ALL               26/38 32.7 (15.3-45.2)   Reference
1 4 ALL               26/38 32.7 (15.3-45.2)   Reference
0 3 AML high risk     45/54 44.8 (33.8-51.5)   0.56 (0.34-0.93)
1 3 AML high risk     45/54 44.8 (33.8-51.5)   0.56 (0.34-0.93)
0 2 AML low risk      24/45 37.3 (11.9-70.1)   0.56 (0.31-1.01)
1 2 AML low risk      24/45 37.3 (11.9-70.1)   0.56 (0.31-1.01)
0 1 Logrank P-value:  0.0541 .                 + Censor
1 1 Logrank P-value:  0.0541 .                 + Censor
;
ods listing gpath="%sysfunc(pathname(work))" image_dpi=300 style=htmlblue;
ods graphics /ATTRPRIORITY=none height=80px width=450px noborder imagename='legend' outputfmt=png;
ods html exclude sgplot;   
/*generate a Legend graph*/
title;
proc sgplot data=legend noborder pad=0 noautolegend;
styleattrs datalinepatterns=(solid longdash dot dot) DATACONTRASTCOLORS=(black red green white);
series x=x y=y/group=treat lineattrs=(thickness=4);
yaxistable treat event median hr/pad=10 labelattrs=(size=16 weight=bold) valuejustify=center valueattrs=(size=14);
xaxis display=none;
yaxis display=none;
run;







%SGANNO;   
data sganno;
%SGIMAGE(image="%sysfunc(pathname(work))\legend.png",   
         drawspace="wallpercent", x1=98, y1=98,width=80,
         anchor="topright",
         border="false"         
         );
run;
ods html dpi=300;
ods graphics / reset=all ATTRPRIORITY=none;
proc sgplot data=SurvivalPlot sganno=sganno noautolegend;
styleattrs datalinepatterns=(solid longdash dot) DATACONTRASTCOLORS=(black red green);
step x=Time y=Survival / group=Stratum lineattrs=(thickness=2);
scatter x=Time y=Censored / group=Stratum markerattrs=(symbol=plus);
run;
 
 
 
 

SGPlot.png

SASuserlot
Barite | Level 11

thank you @JeffMeyers .

Thank you @Ksharp for the code. It worked like a gem.

Catch up on SAS Innovate 2026

Nearly 200 sessions are now available on demand with the SAS Innovate Digital Pass.

Explore Now →
How to Concatenate Values

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.

SAS Training: Just a Click Away

 Ready to level-up your skills? Choose your own adventure.

Browse our catalog!

Discussion stats
  • 6 replies
  • 3003 views
  • 7 likes
  • 5 in conversation