/****************************************************************************************

  • Program: f_14_2_1_1_pfs_km_plot_saffl.sas
  • Output: Figure 14.2.1.1 Kaplan–Meier Plot of Progression-Free Survival (SAF)
  • Inputs: ADaM.ADSL; ADaM.ADTTE (PARAMCD='PFS')
  • Outputs: /outputs/rtf/f_14_2_1_1_pfs_km_plot_saffl.rtf
  • Author: Alpha Traore
  • Date:

****************************************************************************************/

In [2]:
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

In [3]:
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;
SAS Output

The SAS System

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
In [24]:
/*=========================================================
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;
SAS Output
Obs _TYPE_ _FREQ_ MAX
1 0 254 198
In [25]:
/*----------------------------
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;
SAS Output
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
In [26]:
/*----------------------------
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;
SAS Output
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
In [27]:
/*----------------------------
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;
SAS Output
svgtitle The SGPlot Procedure 86 79 75 69 65 61 59 55 50 49 47 47 45 45 42 40 40 39 35 7 0 84 61 48 38 31 22 14 9 7 6 4 4 4 4 4 4 4 3 3 0 84 75 58 42 31 23 20 16 14 13 12 10 8 6 6 6 6 5 5 1 0 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200 Months 0.0 0.2 0.4 0.6 0.8 1.0 Probability of Survival TRT A TRT B TRT C Censored TRT C TRT B TRT A Figure 14.4.1.1 Kaplan-Meier Plot of Overall Survival ITT Population Abbreviations: ITT = Intent-to-Treat; OS = overall survival Notes: Analysis population is the ITT population: Includes all randomized subjects. OS is time from randomization to death; if no death by cutoff, censor at last known alive date. Total N=254; censored=102 (A=57, B=23, C=22); events=152 (A=29, B=61, C=61). The SGPlot Procedure
In [ ]:

In [ ]: