/****************************************************************************************
****************************************************************************************/
options mprint mlogic symbolgen;
options validvarname=upcase;
92 ods listing close;ods html5 (id=saspy_internal) options(bitmap_mode='inline') device=svg style=HTMLBlue; ods graphics on /
92 ! outputfmt=png;
NOTE: Writing HTML5(SASPY_INTERNAL) Body file: sashtml1.htm
93
94 options mprint mlogic symbolgen;
95 options validvarname=upcase;
96 ods html5 (id=saspy_internal) close;ods listing;
97
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;
ITTFL ="Y";
txgrp = ifc(trtan=1, 'TRT A',
ifc(trtan=2, 'TRT B',
ifc(trtan=3, 'TRT C', 'Other')));
run;
proc print data=temp1(obs=5); run;
| Obs | USUBJID | TRT01AN | SAFFL | TRT | TRTAN | PARAM | PARAMCD | AVAL | CNSR | PARAMN | ITTFL | TXGRP |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 01-701-1015 | 1 | Y | 1 | 1 | Progression Free Survival | PFS | 2 | 0 | 1 | Y | TRT A |
| 2 | 01-701-1023 | 1 | Y | 1 | 1 | Progression Free Survival | PFS | 3 | 0 | 1 | Y | TRT A |
| 3 | 01-701-1028 | 2 | Y | 2 | 2 | Progression Free Survival | PFS | 3 | 0 | 1 | Y | TRT B |
| 4 | 01-701-1033 | 3 | Y | 3 | 3 | Progression Free Survival | PFS | 28 | 1 | 1 | Y | TRT C |
| 5 | 01-701-1034 | 2 | Y | 2 | 2 | Progression Free Survival | PFS | 58 | 0 | 1 | Y | TRT B |
/*=========================================================
PFS KM plot program
(uses ITTFL='Y', PARAMCD='PFS', TXGRP, AVAL, CNSR)
=========================================================*/
ods escapechar='^';
/*----------------------------
1) Parameters (edit here)
----------------------------*/
%let FLAG = (ITTFL='Y'); /* analysis population condition */
%let PARAM = PFS; /* PARAMCD */
%let ATR_BY = 10; /* at-risk tick spacing */
%let X_BY = 5; /* x-axis major tick spacing */
/*----------------------------
2) Subset ADTTE for OS + flag
----------------------------*/
data _adtte;
set temp1;
where (&FLAG) and upcase(paramcd)=upcase("&PARAM");
run;
/*----------------------------
3) Treatment totals + censor/event counts
(assumes TXGRP like "TRT A" / "TRT B" -> last token A/B)
----------------------------*/
%let N_A=0; %let N_B=0; %let N_C=0;
%let N_A_CNSR0=0; %let N_A_CNSR1=0;
%let N_B_CNSR0=0; %let N_B_CNSR1=0;
%let N_C_CNSR0=0; %let N_C_CNSR1=0;
proc freq data=_adtte noprint;
tables txgrp / out=_ntrt;
tables txgrp*cnsr / out=_tcnsr;
run;
data _null_;
set _ntrt;
length _g $8;
_g = upcase(scan(txgrp,-1,' ')); /* 'A' or 'B' */
call symputx(cats('N_', _g), count);
run;
data _null_;
set _tcnsr;
length _g $8;
_g = upcase(scan(txgrp,-1,' ')); /* 'A' or 'B' */
call symputx(cats('N_', _g, '_CNSR', cnsr), count);
run;
%let N = %eval(&N_A + &N_B + &N_C);
%let NC11 = &N_A_CNSR1;
%let NC12 = &N_B_CNSR1;
%let NC13 = &N_C_CNSR1;
%let NC01 = &N_A_CNSR0;
%let NC02 = &N_B_CNSR0;
%let NC03 = &N_C_CNSR0;
%let NC1 = %eval(&NC11 + &NC12 + &NC13); /* censored */
%let NC0 = %eval(&NC01 + &NC02 + &NC03); /* events */
/*----------------------------
4) XMAX from max AVAL
----------------------------*/
proc means data=_adtte noprint max;
var aval;
output out=_max max=MAX;
run;
data _null_;
set _max;
call symputx('XMAX', ceil(MAX+3));
run;
proc print data=_max; run;
| Obs | _TYPE_ | _FREQ_ | MAX |
|---|---|---|---|
| 1 | 0 | 254 | 198 |
/*----------------------------
5) LIFETEST -> capture SurvivalPlot as surv_plot
NOTE: time aval*cnsr(1) means CNSR=1 is censored (ADaM style)
----------------------------*/
ods exclude all;
ods output SurvivalPlot=surv_plot;
proc lifetest data=_adtte plots=survival(atrisk=0 to &XMAX by &ATR_BY);
time aval*cnsr(1);
strata txgrp / test=logrank;
run;
ods exclude none;
proc print data=surv_plot (obs=10); run;
| Obs | TIME | SURVIVAL | ATRISK | EVENT | CENSORED | TATRISK | STRATUM | STRATUMNUM |
|---|---|---|---|---|---|---|---|---|
| 1 | 0 | 1.00000 | 86 | 0 | . | . | TRT A | 1 |
| 2 | 0 | . | 86 | . | . | 0 | TRT A | 1 |
| 3 | 1 | 0.98837 | 86 | 1 | . | . | TRT A | 1 |
| 4 | 2 | 0.97674 | 85 | 1 | . | . | TRT A | 1 |
| 5 | 3 | 0.95349 | 84 | 2 | . | . | TRT A | 1 |
| 6 | 7 | 0.94186 | 82 | 1 | . | . | TRT A | 1 |
| 7 | 8 | . | 81 | 0 | 0.94186 | . | TRT A | 1 |
| 8 | 9 | 0.93009 | 80 | 1 | . | . | TRT A | 1 |
| 9 | 10 | . | 79 | . | . | 10 | TRT A | 1 |
| 10 | 12 | . | 79 | 0 | 0.93009 | . | TRT A | 1 |
/*----------------------------
6) Prepare plotting dataset
IMPORTANT: In SurvivalPlot output, Censored is usually the *Y value*
for the censor mark (not a 0/1 flag).
----------------------------*/
/* Check variables if you want */
/*proc contents data=surv_plot; run;*/
data final1;
set surv_plot;
length stratumc $50;
/* Stratum in SurvivalPlot output already holds "TRT A", "TRT B", etc. */
stratumc = strip(Stratum);
/* In SurvivalPlot output, Censored is typically the Y-value for the marker */
cens_y = Censored;
/* sort helper: Survival rows first, then censor marks, then at-risk rows */
_k = (not missing(Survival))*3
+ (not missing(cens_y))*2
+ (not missing(tAtRisk));
run;
proc sort data=final1;
by stratumc time descending _k;
run;
proc print data=final1 (obs=10); run;
| Obs | TIME | SURVIVAL | ATRISK | EVENT | CENSORED | TATRISK | STRATUM | STRATUMNUM | STRATUMC | CENS_Y | _K |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 0 | 1.00000 | 86 | 0 | . | . | TRT A | 1 | TRT A | . | 3 |
| 2 | 0 | . | 86 | . | . | 0 | TRT A | 1 | TRT A | . | 1 |
| 3 | 1 | 0.98837 | 86 | 1 | . | . | TRT A | 1 | TRT A | . | 3 |
| 4 | 2 | 0.97674 | 85 | 1 | . | . | TRT A | 1 | TRT A | . | 3 |
| 5 | 3 | 0.95349 | 84 | 2 | . | . | TRT A | 1 | TRT A | . | 3 |
| 6 | 7 | 0.94186 | 82 | 1 | . | . | TRT A | 1 | TRT A | . | 3 |
| 7 | 8 | . | 81 | 0 | 0.94186 | . | TRT A | 1 | TRT A | 0.94186 | 2 |
| 8 | 9 | 0.93009 | 80 | 1 | . | . | TRT A | 1 | TRT A | . | 3 |
| 9 | 10 | . | 79 | . | . | 10 | TRT A | 1 | TRT A | . | 1 |
| 10 | 12 | . | 79 | 0 | 0.93009 | . | TRT A | 1 | TRT A | 0.93009 | 2 |
/*----------------------------
7) Titles / Footnotes
----------------------------*/
title; footnote;
title1 j=c "^S={font_face='Times New Roman' font_size=12pt font_weight=bold}Figure 14.4.1.1";
title2 j=c "^S={font_face='Times New Roman' font_size=16pt font_weight=bold}Kaplan-Meier Plot of Overall Survival";
title3 j=c "^S={font_face='Times New Roman' font_size=14pt font_weight=bold}ITT Population";
footnote1 j=l "Abbreviations: ITT = Intent-to-Treat; OS = overall survival";
footnote3 j=l
"Notes:^n^{unicode 2022} Analysis population is the ITT population: Includes all randomized subjects."
"^n^{unicode 2022} OS is time from randomization to death; if no death by cutoff, censor at last known alive date."
"^n^{unicode 2022} Total N=&N; censored=&NC1 (A=&NC11, B=&NC12, C=&NC13); events=&NC0 (A=&NC01, B=&NC02, C=&NC02).";
/*----------------------------
8) Plot
----------------------------*/
ods graphics / reset width=10.2in height=5.5in noborder;
proc sgplot data=final1
noautolegend;
styleattrs datacontrastcolors=(blue red green)
datalinepatterns=(solid dash);
/* KM curves (uses rows where Survival is populated) */
step x=time y=survival / group=stratum name='trt' lineattrs=(thickness=2);
/* Censor marks (uses rows where cens_y is populated) */
scatter x=time y=cens_y / markerattrs=(symbol=plus size=8)
name='cens' legendlabel='Censored';
/* At-risk table (uses rows where tAtRisk is populated) */
xaxistable atrisk / class=stratum x=tatrisk location=inside;
keylegend 'cens' / location=inside position=topright opaque;
keylegend 'trt' / location=inside position=bottom;
xaxis min=0 max=&XMAX values=(0 to &XMAX by &X_BY) valueshint label="Months";
yaxis min=0 max=1 offsetmin=0.08 offsetmax=0.08 label="Probability of Survival";
run;
title; footnote;