Program: f_14_2_x_x_pfs_km_plot_saffl.sas
Study: MYFAKESTUDY
Output: Figure 14.2.x.x Kaplan–Meier Plot of Progression-Free Survival by Treatment Group
Population: Safety Analysis Set (SAFFL='Y')
14_2_x_x
Create the Kaplan–Meier PFS figure per SAP, including:
PARAMCD='PFS' or per SAP)/outputs/rtf/f_14_2_x_x_pfs_km_plot_saffl.rtf/outputs/datasets/f_14_2_x_x_kmplotdata.sas7bdatUSUBJID, TRT01A / TRT01AN, SAFFLUSUBJID, PARAMCD, AVAL (time), CNSRKM estimation via PROC LIFETEST (or SGRENDER from KM outputs):
TIMELIST= and (template) BLOCKPLOT in INNERMARGINAuthor: Alpha Traore
Date: <YYYY-MM-DD>
QC Program: qc_f_14_2_x_x_pfs_km_plot_saffl.sas
| Version | Date | By | Description |
|---|---|---|---|
| v1.0 | YYYY-MM-DD | <Name> | Initial version |
| v1.1 | YYYY-MM-DD | <Name> | <What changed> |
Main program steps (outline)
options mprint mlogic symbolgen;
options validvarname=upcase;
227 ods listing close;ods html5 (id=saspy_internal) options(bitmap_mode='inline') device=svg style=HTMLBlue; ods graphics on /
227! outputfmt=png;
NOTE: Writing HTML5(SASPY_INTERNAL) Body file: sashtml13.htm
228
229 options mprint mlogic symbolgen;
230 options validvarname=upcase;
231 ods html5 (id=saspy_internal) close;ods listing;
232
Read the ADaM Datasets Required for Table Generation: ADSL and ADTTE
libname aa "/export/viya/homes/alpha@alphatraore.com/toydata";
/* proc datasets lib=aa; quit;*/
proc sort data=aa.adsl out = adsl (obs=5); by usubjid; run;
proc sort data=aa.adtte out = adtte (obs=5); by usubjid; run;
data subj;
set adsl;
where saffl="Y";
trt=trt01an;
run;
data tte;
set adtte;
trt=trtan;
run;
proc sort data=subj; by usubjid; run;
proc sort data=tte; by usubjid; run;
data temp1;
merge subj tte;
by usubjid;
run;
proc print data=temp1(obs=5); run;
| Obs | USUBJID | TRT01AN | SAFFL | TRT | TRTAN | PARAM | PARAMCD | AVAL | CNSR | PARAMN |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 01-701-1015 | 1 | Y | 1 | 1 | Progression Free Survival | PFS | 2 | 0 | 1 |
| 2 | 01-701-1023 | 1 | Y | 1 | 1 | Progression Free Survival | PFS | 3 | 0 | 1 |
| 3 | 01-701-1028 | 2 | Y | 2 | 2 | Progression Free Survival | PFS | 3 | 0 | 1 |
| 4 | 01-701-1033 | 3 | Y | 3 | 3 | Progression Free Survival | PFS | 28 | 1 | 1 |
| 5 | 01-701-1034 | 2 | Y | 2 | 2 | Progression Free Survival | PFS | 58 | 0 | 1 |
alphaqt=: significance level for quartile (e.g., median) confidence limitsmethod= / conftype=: KM method and CI typeplots=: request failure plot (1–S(t)) with numbers at risktimelist=: specify reporting/display time points and limit ProductLimitEstimates rowsProduce the KM failure plot with time-point markers on the x-axis and numbers at risk shown below the x-axis at the specified timelist= time points.
proc lifetest data=temp1
alphaqt=0.05
method=km
plots=survival(failure atrisk=0 to 200 by 25 )
timelist=(0 to 200 by 25)
conftype=linear ;
time aval*cnsr(1);
strata trtan;
ods output FailurePlot = sur_fail ProductLimitEstimates=estimates;
run;
The LIFETEST Procedure
Stratum 1: Actual Treatment (N) = 1
| Product-Limit Survival Estimates | |||||||
|---|---|---|---|---|---|---|---|
| Timelist | AVAL | Survival | Failure | Survival Standard Error | Number Failed |
Number Left |
|
| 0.000 | 0.000 | 1.0000 | 0 | 0 | 0 | 86 | |
| 25.000 | 24.000 | 0.8811 | 0.1189 | 0.0353 | 10 | 72 | |
| 50.000 | 48.000 | 0.7812 | 0.2188 | 0.0457 | 18 | 61 | |
| 75.000 | 70.000 | 0.7414 | 0.2586 | 0.0488 | 21 | 53 | |
| 100.000 | 97.000 | 0.6575 | 0.3425 | 0.0540 | 27 | 47 | |
| 125.000 | 110.000 | 0.6435 | 0.3565 | 0.0546 | 28 | 45 | |
| 150.000 | 110.000 | 0.6435 | 0.3565 | 0.0546 | 28 | 40 | |
| 175.000 | 110.000 | 0.6435 | 0.3565 | 0.0546 | 28 | 37 | |
| 200.000 | . | . | . | . | 29 | 0 | |
Summary Statistics for Time Variable AVAL
| Quartile Estimates | ||||
|---|---|---|---|---|
| Percent | Point Estimate |
95% Confidence Interval | ||
| Transform | [Lower | Upper) | ||
| 75 | . | LINEAR | . | . |
| 50 | . | LINEAR | . | . |
| 25 | 70.000 | LINEAR | 35.000 | 177.000 |
| Mean | Standard Error |
|---|---|
| 129.866 | 7.534 |
The mean survival time and its standard error were underestimated because the largest observation was censored and the estimation was restricted to the largest event time.
The LIFETEST Procedure
Stratum 2: Actual Treatment (N) = 2
| Product-Limit Survival Estimates | |||||||
|---|---|---|---|---|---|---|---|
| Timelist | AVAL | Survival | Failure | Survival Standard Error | Number Failed |
Number Left |
|
| 0.000 | 0.000 | 1.0000 | 0 | 0 | 0 | 84 | |
| 25.000 | 25.000 | 0.5883 | 0.4117 | 0.0566 | 32 | 41 | |
| 50.000 | 50.000 | 0.3417 | 0.6583 | 0.0576 | 48 | 21 | |
| 75.000 | 71.000 | 0.1609 | 0.8391 | 0.0490 | 58 | 7 | |
| 100.000 | 96.000 | 0.0919 | 0.9081 | 0.0411 | 61 | 4 | |
| 125.000 | 96.000 | 0.0919 | 0.9081 | 0.0411 | 61 | 4 | |
| 150.000 | 96.000 | 0.0919 | 0.9081 | 0.0411 | 61 | 4 | |
| 175.000 | 96.000 | 0.0919 | 0.9081 | 0.0411 | 61 | 3 | |
| 200.000 | . | . | . | . | 61 | 0 | |
Summary Statistics for Time Variable AVAL
| Quartile Estimates | ||||
|---|---|---|---|---|
| Percent | Point Estimate |
95% Confidence Interval | ||
| Transform | [Lower | Upper) | ||
| 75 | 58.000 | LINEAR | 47.000 | 89.000 |
| 50 | 36.000 | LINEAR | 24.000 | 46.000 |
| 25 | 14.000 | LINEAR | 5.000 | 22.000 |
| Mean | Standard Error |
|---|---|
| 40.075 | 3.689 |
The mean survival time and its standard error were underestimated because the largest observation was censored and the estimation was restricted to the largest event time.
The LIFETEST Procedure
Stratum 3: Actual Treatment (N) = 3
| Product-Limit Survival Estimates | |||||||
|---|---|---|---|---|---|---|---|
| Timelist | AVAL | Survival | Failure | Survival Standard Error | Number Failed |
Number Left |
|
| 0.000 | 0.000 | 1.0000 | 0 | 0 | 0 | 84 | |
| 25.000 | 25.000 | 0.6390 | 0.3610 | 0.0538 | 29 | 49 | |
| 50.000 | 48.000 | 0.3761 | 0.6239 | 0.0565 | 48 | 23 | |
| 75.000 | 71.000 | 0.2751 | 0.7249 | 0.0545 | 54 | 15 | |
| 100.000 | 100.000 | 0.2018 | 0.7982 | 0.0508 | 58 | 11 | |
| 125.000 | 119.000 | 0.1467 | 0.8533 | 0.0458 | 61 | 7 | |
| 150.000 | 126.000 | 0.1258 | 0.8742 | 0.0438 | 62 | 6 | |
| 175.000 | 126.000 | 0.1258 | 0.8742 | 0.0438 | 62 | 5 | |
| 200.000 | . | . | . | . | 62 | 0 | |
Summary Statistics for Time Variable AVAL
| Quartile Estimates | ||||
|---|---|---|---|---|
| Percent | Point Estimate |
95% Confidence Interval | ||
| Transform | [Lower | Upper) | ||
| 75 | 80.000 | LINEAR | 51.000 | 119.000 |
| 50 | 33.000 | LINEAR | 27.000 | 48.000 |
| 25 | 19.000 | LINEAR | 15.000 | 25.000 |
| Mean | Standard Error |
|---|---|
| 51.706 | 4.941 |
The mean survival time and its standard error were underestimated because the largest observation was censored and the estimation was restricted to the largest event time.
| Summary of the Number of Censored and Uncensored Values | |||||
|---|---|---|---|---|---|
| Stratum | TRTAN | Total | Failed | Censored | Percent Censored |
| 1 | 1 | 86 | 29 | 57 | 66.28 |
| 2 | 2 | 84 | 61 | 23 | 27.38 |
| 3 | 3 | 84 | 62 | 22 | 26.19 |
| Total | 254 | 152 | 102 | 40.16 | |
The LIFETEST Procedure
Testing Homogeneity of Survival Curves for AVAL over Strata
| Rank Statistics | ||
|---|---|---|
| TRTAN | Log-Rank | Wilcoxon |
| 1 | -45.268 | -6456.0 |
| 2 | 25.627 | 3984.0 |
| 3 | 19.641 | 2472.0 |
| Covariance Matrix for the Log-Rank Statistics | |||
|---|---|---|---|
| TRTAN | 1 | 2 | 3 |
| 1 | 34.8938 | -15.5885 | -19.3053 |
| 2 | -15.5885 | 25.8988 | -10.3102 |
| 3 | -19.3053 | -10.3102 | 29.6155 |
| Covariance Matrix for the Wilcoxon Statistics | |||
|---|---|---|---|
| TRTAN | 1 | 2 | 3 |
| 1 | 984202 | -458582 | -525619 |
| 2 | -458582 | 828329 | -369746 |
| 3 | -525619 | -369746 | 895366 |
| Test of Equality over Strata | |||
|---|---|---|---|
| Test | Chi-Square | DF | Pr > Chi-Square |
| Log-Rank | 60.2696 | 2 | <.0001 |
| Wilcoxon | 43.8983 | 2 | <.0001 |
| -2Log(LR) | 100.9378 | 2 | <.0001 |
title "FailurePlot";
proc print data=sur_fail (obs=5); run;
| Obs | TIME | SURVIVAL | ATRISK | EVENT | CENSORED | TATRISK | STRATUM | STRATUMNUM | _1_SURVIVAL_ | _1_CENSORED_ |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 0 | 1.00000 | 86 | 0 | . | . | 1 | 1 | 0.000000 | . |
| 2 | 0 | . | 86 | . | . | 0 | 1 | 1 | . | . |
| 3 | 1 | 0.98837 | 86 | 1 | . | . | 1 | 1 | 0.011628 | . |
| 4 | 2 | 0.97674 | 85 | 1 | . | . | 1 | 1 | 0.023256 | . |
| 5 | 3 | 0.95349 | 84 | 2 | . | . | 1 | 1 | 0.046512 | . |
title "ProductLimitEstimates";
proc print data=estimates (obs=5); run;
| Obs | STRATUM | TRTAN | TIMELIST | AVAL | CENSOR | SURVIVAL | FAILURE | STDERR | FAILED | LEFT |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 1 | 1 | 0.000 | 0.000 | 0 | 1.0000 | 0 | 0 | 0 | 86 |
| 2 | 1 | 1 | 25.000 | 24.000 | 0 | 0.8811 | 0.1189 | 0.0353 | 10 | 72 |
| 3 | 1 | 1 | 50.000 | 48.000 | 0 | 0.7812 | 0.2188 | 0.0457 | 18 | 61 |
| 4 | 1 | 1 | 75.000 | 70.000 | 0 | 0.7414 | 0.2586 | 0.0488 | 21 | 53 |
| 5 | 1 | 1 | 100.000 | 97.000 | 0 | 0.6575 | 0.3425 | 0.0540 | 27 | 47 |
Definition: Number at risk = count of subjects who have not experienced the event and have not been censored immediately prior to a given time point.
Programming note: Display the number at risk at selected time points on the Kaplan–Meier (failure/cumulative incidence) plot.
data est(rename=(timelist=time stratum=stratumnum));
set estimates(keep=stratum timelist left );
run;
proc print data=est (obs=8); run;
| Obs | STRATUMNUM | TIME | LEFT |
|---|---|---|---|
| 1 | 1 | 0.000 | 86 |
| 2 | 1 | 25.000 | 72 |
| 3 | 1 | 50.000 | 61 |
| 4 | 1 | 75.000 | 53 |
| 5 | 1 | 100.000 | 47 |
| 6 | 1 | 125.000 | 45 |
| 7 | 1 | 150.000 | 40 |
| 8 | 1 | 175.000 | 37 |
Generate a Kaplan–Meier figure that includes:
Time-point block plot (tick/marker display):
Marks the requested time points along the x-axis for consistent visual reference.
Numbers at risk:
Displays the number of subjects at risk immediately prior to each selected time point beneath the x-axis.
Implementation notes
PLOTS= to request the failure plot and the at-risk table.TIMELIST= to align the displayed/reporting time points across the plot and output datasets.proc sort data=sur_fail;
by stratumnum time;
run;
data new_survival (drop=j);
merge est sur_fail ;
length param_t $100;
by stratumnum time;
do j = 0 to 200 by 25;
if time = j then do;
blkrsk = put(left,3.);
blkx = time;
end;
end;
trtan = stratumnum;
param_t="parameter name";
paramn=1;
run;
proc sort data=new_survival;
by trtan time;
run;
title "Time-point block plot and numbers at risk";
proc print data=new_survival (obs=10); run;
| Obs | STRATUMNUM | TIME | LEFT | SURVIVAL | ATRISK | EVENT | CENSORED | TATRISK | STRATUM | _1_SURVIVAL_ | _1_CENSORED_ | PARAM_T | BLKRSK | BLKX | TRTAN | PARAMN |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 1 | 0.000 | 86 | 1.00000 | 86 | 0 | . | . | 1 | 0.000000 | . | parameter name | 86 | 0 | 1 | 1 |
| 2 | 1 | 0.000 | 86 | . | 86 | . | . | 0 | 1 | . | . | parameter name | 86 | 0 | 1 | 1 |
| 3 | 1 | 1.000 | . | 0.98837 | 86 | 1 | . | . | 1 | 0.011628 | . | parameter name | . | 1 | 1 | |
| 4 | 1 | 2.000 | . | 0.97674 | 85 | 1 | . | . | 1 | 0.023256 | . | parameter name | . | 1 | 1 | |
| 5 | 1 | 3.000 | . | 0.95349 | 84 | 2 | . | . | 1 | 0.046512 | . | parameter name | . | 1 | 1 | |
| 6 | 1 | 7.000 | . | 0.94186 | 82 | 1 | . | . | 1 | 0.058140 | . | parameter name | . | 1 | 1 | |
| 7 | 1 | 8.000 | . | . | 81 | 0 | 0.94186 | . | 1 | . | 0.058140 | parameter name | . | 1 | 1 | |
| 8 | 1 | 9.000 | . | 0.93009 | 80 | 1 | . | . | 1 | 0.069913 | . | parameter name | . | 1 | 1 | |
| 9 | 1 | 12.000 | . | . | 79 | 0 | 0.93009 | . | 1 | . | 0.069913 | parameter name | . | 1 | 1 | |
| 10 | 1 | 13.000 | . | . | 78 | 0 | 0.93009 | . | 1 | . | 0.069913 | parameter name | . | 1 | 1 |
PROC TEMPLATE for the Kaplan–Meier Graph¶ENTRYTITLE to define the graph title (and optional subtitle).BLOCKPLOT at selected time points.STEPPLOT for the KM curveSCATTERPLOT for censored observations (and/or time-point markers)proc template;
define statgraph kmplot;
dynamic x_var y_var1 y_var2 ;
begingraph;
*Graph title;
entrytitle "Category = Time to Event" /
textattrs=(size=9pt ) pad=(bottom=20px);
*Colour map;
discreteattrmap name='colors' / ignorecase=true;
value 'Placebo' / lineattrs=(color=blue pattern=shortdash) markerattrs=(color=blue symbol=trianglefilled);
value 'Low Dose' / lineattrs=(color=red pattern=shortdash) markerattrs=(color=red symbol=circlefilled);
value 'High Dose' / lineattrs=(color=green pattern=shortdash) markerattrs=(color=green symbol=squarefilled);
enddiscreteattrmap;
discreteattrvar attrvar=gmarker var=trtan attrmap='colors';
%*Start KM plot*;
layout overlay /
xaxisopts=(Label="Time at Risk (days)"
display=(tickvalues line label ticks )
type=linear
linearopts=(tickvaluesequence=(start=0 end=200 increment=25)
viewmin=0 viewmax=200))
yaxisopts=( Label="Cumulative Incidence of Subjects with Event"
type=linear linearopts= (viewmin=0 viewmax=1) );
StepPlot X=x_var Y=y_var1 / primary=true Group=gmarker
LegendLabel="Cumulative Incidence of Subjects with Event" NAME="STEP";
%*Censored observations are suppressed but can be added here*;
scatterPlot X=x_var Y=y_var2 / Group=gmarker markerattrs=(symbol=plus)
LegendLabel="Censored" NAME="SCATTER";
Mergedlegend "STEP" "SCATTER"/
location=inside halign=left valign=top across=1 valueattrs=(family="Arial" size=8pt);
referenceline y=0;
%*Start at risk value plot;
innermargin;
axistable x=blkx value=blkrsk / class=gmarker colorgroup=gmarker
display=(label)
labelattrs=(family="Arial" size=8pt)
valueattrs=(family="Arial" size=8pt);
endinnermargin;
endlayout;
endgraph;
end;
run;
328 ods listing close;ods html5 (id=saspy_internal) options(bitmap_mode='inline') device=svg style=HTMLBlue; ods graphics on / 328! outputfmt=png; NOTE: Writing HTML5(SASPY_INTERNAL) Body file: sashtml20.htm 329 330 proc template; 331 define statgraph kmplot; 332 dynamic x_var y_var1 y_var2 ; 333 begingraph; 334 *Graph title; 335 entrytitle "Category = Time to Event" / 336 textattrs=(size=9pt ) pad=(bottom=20px); 337 338 *Colour map; 339 discreteattrmap name='colors' / ignorecase=true; 340 value 'Placebo' / lineattrs=(color=blue pattern=shortdash) markerattrs=(color=blue symbol=trianglefilled); 341 value 'Low Dose' / lineattrs=(color=red pattern=shortdash) markerattrs=(color=red symbol=circlefilled); 342 value 'High Dose' / lineattrs=(color=green pattern=shortdash) markerattrs=(color=green symbol=squarefilled); 343 enddiscreteattrmap; 344 345 discreteattrvar attrvar=gmarker var=trtan attrmap='colors'; 346 347 %*Start KM plot*; 348 layout overlay / 349 xaxisopts=(Label="Time at Risk (days)" 350 display=(tickvalues line label ticks ) 351 type=linear 352 linearopts=(tickvaluesequence=(start=0 end=200 increment=25) 353 viewmin=0 viewmax=200)) 354 yaxisopts=( Label="Cumulative Incidence of Subjects with Event" 355 type=linear linearopts= (viewmin=0 viewmax=1) ); 356 357 StepPlot X=x_var Y=y_var1 / primary=true Group=gmarker 358 LegendLabel="Cumulative Incidence of Subjects with Event" NAME="STEP"; 359 360 %*Censored observations are suppressed but can be added here*; 361 scatterPlot X=x_var Y=y_var2 / Group=gmarker markerattrs=(symbol=plus) 362 LegendLabel="Censored" NAME="SCATTER"; 363 364 Mergedlegend "STEP" "SCATTER"/ 365 location=inside halign=left valign=top across=1 valueattrs=(family="Arial" size=8pt); 366 referenceline y=0; 367 368 %*Start at risk value plot; 369 innermargin; 370 axistable x=blkx value=blkrsk / class=gmarker colorgroup=gmarker 371 display=(label) 372 labelattrs=(family="Arial" size=8pt) 373 valueattrs=(family="Arial" size=8pt); 374 375 376 endinnermargin; 377 endlayout; 378 endgraph; 379 end; NOTE: Overwriting existing template/link: Kmplot NOTE: STATGRAPH 'Kmplot' has been saved to: WORK.TEMPLAT 380 run; NOTE: PROCEDURE TEMPLATE used (Total process time): real time 0.00 seconds cpu time 0.00 seconds 381 ods html5 (id=saspy_internal) close;ods listing; 382
proc format;
value trtanf
1="Placebo"
2="TRT A"
3="TRT B"
;
run;
383 ods listing close;ods html5 (id=saspy_internal) options(bitmap_mode='inline') device=svg style=HTMLBlue; ods graphics on / 383! outputfmt=png; NOTE: Writing HTML5(SASPY_INTERNAL) Body file: sashtml21.htm 384 385 proc format; 386 value trtanf 387 1="Placebo" 388 2="TRT A" 389 3="TRT B" 390 ; NOTE: Format TRTANF is already on the library WORK.FORMATS. NOTE: Format TRTANF has been output. 391 run; NOTE: PROCEDURE FORMAT used (Total process time): real time 0.00 seconds cpu time 0.00 seconds 392 ods html5 (id=saspy_internal) close;ods listing; 393
ods listing image_dpi=300;
ods graphics on / reset=all border=off
height=13cm width=14.72cm
antialiasmax=20000;
title1 "Cumulative Incidence Plot";
title2 "Safety Analysis Set";
/* Generate the graph using sgrender procedure */
proc sgrender data=new_survival template=kmplot;
dynamic x_var="time" y_var1="_1_survival_" y_var2="_1_censored_" ;
format trtan trtanf.;
run;
ods graphics off;
ods rtf close;
title;
footnote;
ods graphics off;