PFS Summary Table (SAFFL) — Table 14.2.x.x¶

Program: t_14_2_x_pfs_summary_saffl.sas
Population: Safety Analysis Set (SAFFL='Y')
Author: Alpha Traore


Purpose¶

Create the PFS summary table per SAP, including (as applicable):

  • Subjects with event and censored, n (%)
  • Kaplan–Meier median PFS with 95% CI
  • Quartiles (Q1, Q3) with 95% CI (if required)
  • KM event-free rates at selected time points (e.g., 6/12/18 months) with 95% CI
  • Optional:
    • Log-rank p-value
    • Cox model hazard ratio (HR) with 95% CI

Inputs¶

  • ADaM.ADSL — treatment, flags/denominators
  • ADaM.ADTTE — time-to-event records (PARAMCD='PFS' or per SAP)

Key Variables¶

ADSL

  • USUBJID
  • TRT01A / TRT01AN
  • SAFFL

ADTTE

  • USUBJID
  • PARAMCD
  • AVAL (time)
  • CNSR (censoring indicator)

Methods¶

Kaplan–Meier Estimation (PROC LIFETEST)¶

  • Time-to-event setup:
    • TIME AVAL*CNSR(1);
    • STRATA TRT01AN;
  • Event/Censor counts from ADTTE:
    • CNSR=0 → event
    • CNSR=1 → censored (per SAP)
  • Outputs:
    • Median and quartiles: ODS Quartiles
    • Time-point estimates: ODS ProductLimitEstimates using TIMELIST=...

Cox Proportional Hazards (PROC PHREG)¶

  • Estimate HR with 95% CI

Programming for the Task¶

  • Build analysis dataset: merge ADSL + ADTTE (PFS); filter SAFFL='Y' and PARAMCD='PFS'.
  • Derive event/censor n (%) by treatment (denoms from ADSL).
  • Run PROC LIFETEST to get median/quartiles and KM rates at Day 0/50/100/150 (with 95% CI).
  • Run PROC PHREG for HR (95% CI) (and p-value if required).
  • Assemble final table rows/labels and output RTF/PDF with titles/footnotes.
In [ ]:
options mprint mlogic symbolgen; 
options validvarname=upcase;

Read the ADaM Datasets Required for Table Generation: ADSL and ADTTE

In [59]:
libname aa "/export/viya/homes/alpha@alphatraore.com/toydata";

/* proc datasets lib=aa; quit;*/   


proc sort data=aa.adsl  out = adsl  ;  by usubjid; run;
proc sort data=aa.adtte out = adtte ; 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 (where=(TRT in (1,2)) );
    merge subj tte;
    by usubjid;
run;
proc print data=temp1(obs=5); run;
SAS Output

The SAS System

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-1034 2 Y 2 2 Progression Free Survival PFS 58 0 1
5 01-701-1047 1 Y 1 1 Progression Free Survival PFS 46 1 1

Create macro variables to hold treatment totals

In [83]:
%let n1=0;
%let n2=0;
proc sql noprint;
   select count(distinct usubjid) into :n1 from subj where trt=1;
   select count(distinct usubjid) into :n2 from subj where trt=2;
quit;

%put &=n1 &=n2;
1940  ods listing close;ods html5 (id=saspy_internal) options(bitmap_mode='inline') device=svg style=HTMLBlue; ods graphics on /
1940! outputfmt=png;
NOTE: Writing HTML5(SASPY_INTERNAL) Body file: sashtml95.htm
1941  
1942  %let n1=0;
1943  %let n2=0;
1944  proc sql noprint;
1945     select count(distinct usubjid) into :n1 from subj where trt=1;
1946     select count(distinct usubjid) into :n2 from subj where trt=2;
1947  quit;
NOTE: PROCEDURE SQL used (Total process time):
      real time           0.00 seconds
      cpu time            0.00 seconds
      

SYMBOLGEN:  Macro variable N1 resolves to       86
SYMBOLGEN:  Macro variable N2 resolves to       84
1948  
1949  %put &=n1 &=n2;
N1=      86 N2=      84
1950  ods html5 (id=saspy_internal) close;ods listing;
1951  


Obtain Survival Statistics (PROC LIFETEST)¶

Outputs

  • Product-limit estimates (Kaplan–Meier): survival function and standard error (as applicable)
  • Censoring summary (for crude incidence)
  • Quartiles: Q1, Q2 (Median), Q3
In [67]:
proc sort data=temp1;
   by trt;
run;
 
ods output quartiles=quar100;
ods output censoredsummary=cens100;
ods output productlimitestimates=est100;
 
proc lifetest data=temp1 method=km timelist=(0 to 150 by 50) conftype=linear reduceout;
   time aval*cnsr(0);
   strata trt;
   survival out=surv100;
run;
SAS Output

The SAS System

The LIFETEST Procedure

 

Stratum 1: TRT = 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
50.000 46.000   0.9080 0.0920 0.0333 7 61
100.000 70.000   0.8318 0.1682 0.0446 12 47
150.000 148.000   0.7233 0.2767 0.0567 18 40

Summary Statistics for Time Variable AVAL

Quartile Estimates
Percent Point
Estimate
95% Confidence Interval
Transform [Lower Upper)
75 184.000 LINEAR 183.000 189.000
50 183.000 LINEAR 181.000 183.000
25 142.000 LINEAR 70.000 181.000
Mean Standard
Error
155.141 6.524

The SAS System

The LIFETEST Procedure

 

Stratum 2: TRT = 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
50.000 44.000   0.7373 0.2627 0.0611 15 21
100.000 69.000   0.5241 0.4759 0.1010 19 4
150.000 69.000   0.5241 0.4759 0.1010 19 4

Summary Statistics for Time Variable AVAL

Quartile Estimates
Percent Point
Estimate
95% Confidence Interval
Transform [Lower Upper)
75 188.000 LINEAR 167.000 .
50 167.000 LINEAR 63.000 188.000
25 44.000 LINEAR 22.000 69.000
Mean Standard
Error
113.996 13.519
Summary of the Number of Censored and Uncensored Values
Stratum TRT Total Failed Censored Percent
Censored
1 1 86 57 29 33.72
2 2 84 23 61 72.62
Total   170 80 90 52.94

The SAS System

The LIFETEST Procedure

Testing Homogeneity of Survival Curves for AVAL over Strata

Rank Statistics
TRT Log-Rank Wilcoxon
1 -9.0694 -974.00
2 9.0694 974.00
Covariance Matrix for the Log-Rank Statistics
TRT 1 2
1 9.13610 -9.13610
2 -9.13610 9.13610
Covariance Matrix for the Wilcoxon Statistics
TRT 1 2
1 97765.0 -97765.0
2 -97765.0 97765.0
Test of Equality over Strata
Test Chi-Square DF Pr >
Chi-Square
Log-Rank 9.0032 1 0.0027
Wilcoxon 9.7036 1 0.0018
-2Log(LR) 1.1005 1 0.2941
Product-Limit Survival Curves

Process Censoring Summary Data¶

Purpose

  • The dataset contains censoring %; therefore, Event % = 100 − Censored %
  • Create a display-ready cell by concatenating:
    • n with event and the corresponding event % (e.g., 57 (66.3%))
In [65]:
data cens101;
   set cens100;
   where not missing(trt);
   pctfail=100-pctcens;
   length cp $30 label $200;
   cp=put(failed,3.)||" ("||put(pctfail,5.1)||"%)";
   label="Event (Progression or Death)"; group=1; order=1; output;
   cp=put(censored,3.)||" ("||put(pctcens,5.1)||"%)";
   label="Censored"; order=2; output;
   keep group order label trt cp;
run;

proc print data = cens101; run;
SAS Output

The SAS System

Obs TRT CP LABEL GROUP ORDER
1 1 57 ( 66.3%) Event (Progression or Death) 1 1
2 1 29 ( 33.7%) Censored 1 2
3 2 23 ( 27.4%) Event (Progression or Death) 1 1
4 2 61 ( 72.6%) Censored 1 2

Process Quartiles Dataset¶

Purpose

  • For quartile results, if Estimate, Lower CI, or Upper CI is missing, display as NE (Not Estimable)
In [66]:
data quar101;
   set quar100;
   length cp ul ll est $30 label $200;
   if not missing(estimate) then est=put(round(estimate,0.1),5.1);
   else est="NE";
   if not missing(lowerlimit) then ll=put(round(lowerlimit,0.01),6.2);
   else ll="NE";
   if not missing(upperlimit) then ul=put(round(upperlimit,0.01),6.2);
   else ul="NE";
 
   cp=strip(est)||" ("||strip(ll)||", "||strip(ul)||")";
   order=percent;
   if percent=25 then label="25th Percentile";
   else if percent=50 then label="50th Percentile";
   else if percent=75 then label="75th Percentile";
   group=2;
   keep group order label trt cp;
run;
 
proc print data =quar101; run;
SAS Output

The SAS System

Obs TRT CP LABEL ORDER GROUP
1 1 184.0 (183.00, 189.00) 75th Percentile 75 2
2 1 183.0 (181.00, 183.00) 50th Percentile 50 2
3 1 142.0 (70.00, 181.00) 25th Percentile 25 2
4 2 188.0 (167.00, NE) 75th Percentile 75 2
5 2 167.0 (63.00, 188.00) 50th Percentile 50 2
6 2 44.0 (22.00, 69.00) 25th Percentile 25 2

Survival Summary (KM Estimates)¶

  • Convert survival and 95% CI to percent strings; use NE if missing
  • Create time-point labels (0/50/100/150 days)
  • Build display value: Estimate (Lower, Upper) and set sort order
In [68]:
data surv101;
   set surv100;
   length cp ul ll est $30 label $200;
   if not missing(survival) then est=put(round(survival*100,0.1),6.1);
   else est="NE";
   if not missing(sdf_lcl) then ll=put(round(sdf_lcl*100,0.01),7.2);
   else ll="NE";
   if not missing(sdf_ucl) then ul=put(round(sdf_ucl*100,0.01),7.2);
   else ul="NE";
 
   if timelist=50 then label="50 (days)";
   else if timelist=100 then label="100 (days)";
   else if timelist=150 then label="150 (days)";
   else if timelist=0 then label="0 (days)";
 
   cp=strip(est)||" ("||strip(ll)||", "||strip(ul)||")";
   group=3;
   if timelist ne 0 then order=timelist;
   else order=1;
   keep group order label trt cp;
run;
 
proc print data =surv101; run;
SAS Output

The SAS System

Obs TRT CP LABEL GROUP ORDER
1 1 100.0 (100.00, 100.00) 0 (days) 3 1
2 1 90.8 (84.28, 97.32) 50 (days) 3 50
3 1 83.2 (74.43, 91.93) 100 (days) 3 100
4 1 72.3 (61.22, 83.44) 150 (days) 3 150
5 2 100.0 (100.00, 100.00) 0 (days) 3 1
6 2 73.7 (61.76, 85.71) 50 (days) 3 50
7 2 52.4 (32.61, 72.21) 100 (days) 3 100
8 2 52.4 (32.61, 72.21) 150 (days) 3 150

Get Hazard Ratio (Cox PH Model)¶

  • Run PROC PHREG to estimate the treatment effect on time-to-event
  • Capture ParameterEstimates (ODS OUTPUT) and report the Hazard Ratio with 95% CI (Wald)
In [70]:
ods output parameterestimates=hazard100;
 
proc phreg data = temp1;
class trt(ref="1");
model aval*cnsr(1)=trt/ties=efron rl=wald;
run;

proc print data=hazard100; run;
SAS Output

The SAS System

The PHREG Procedure

Model Information
Data Set WORK.TEMP1  
Dependent Variable AVAL Analysis Value
Censoring Variable CNSR Censor
Censoring Value(s) 1  
Ties Handling EFRON  
Number of Observations Read
Number of Observations Used
170
170
Class Level Information
Class Value Design Variables
TRT 1 0
  2 1
Summary of the Number of Event and Censored Values
Total Event Censored Percent
Censored
170 90 80 47.06
Convergence Status
Convergence criterion (GCONV=1E-8) satisfied.
Model Fit Statistics
Criterion Without
Covariates
With
Covariates
-2 LOG L 835.141 785.925
AIC 835.141 787.925
SBC 835.141 790.425
Testing Global Null Hypothesis: BETA=0
Test Chi-Square DF Pr > ChiSq
Likelihood Ratio 49.2153 1 <.0001
Score 52.4128 1 <.0001
Wald 44.6925 1 <.0001
Type 3 Tests
Effect DF Wald Chi-Square Pr > ChiSq
TRT 1 44.6925 <.0001
Analysis of Maximum Likelihood Estimates
Parameter   DF Parameter
Estimate
Standard
Error
Chi-Square Pr > ChiSq Hazard
Ratio
95% Hazard Ratio Confidence Limits Label
TRT 2 1 1.59335 0.23834 44.6925 <.0001 4.920 3.084 7.850 TRT 2

The SAS System

Obs PARAMETER CLASSVAL0 DF ESTIMATE STDERR CHISQ PROBCHISQ HAZARDRATIO HRLOWERCL HRUPPERCL LABEL
1 TRT 2 1 1.59335 0.23834 44.6925 <.0001 4.920 3.084 7.850 TRT 2
In [72]:
data hazard101;
   set hazard100 (drop=label);
   length hazard est ll ul  $30 ;

   if hazardratio ne . then est=put(round(hazardratio,0.1),6.1);
   else est="NE";
   if hrlowercl ne . then ll=put(round(hrlowercl,0.1),6.1);
   else ll="NE";
   if hruppercl ne . then ul=put(round(hruppercl,0.1),6.1);
   else ul="NE";
   group=4;
   order=1;
   label="Hazard Ratio - Estimate (95% CI)";
   hazard=strip(est)||" ("||strip(ll)||","||strip(ul)||")";
run;

proc print data=hazard101; run;
SAS Output

The SAS System

Obs PARAMETER CLASSVAL0 DF ESTIMATE STDERR CHISQ PROBCHISQ HAZARDRATIO HRLOWERCL HRUPPERCL HAZARD EST LL UL GROUP ORDER LABEL
1 TRT 2 1 1.59335 0.23834 44.6925 <.0001 4.920 3.084 7.850 4.9 (3.1,7.8) 4.9 3.1 7.8 4 1 Hazard Ratio - Estimate (95% CI)

Combine Processed Outputs (Final Reporting Dataset)¶

In [74]:
data summary100;
   set cens101 quar101 surv101;
run;

proc print data =summary100; run;
SAS Output

The SAS System

Obs TRT CP LABEL GROUP ORDER
1 1 57 ( 66.3%) Event (Progression or Death) 1 1
2 1 29 ( 33.7%) Censored 1 2
3 2 23 ( 27.4%) Event (Progression or Death) 1 1
4 2 61 ( 72.6%) Censored 1 2
5 1 184.0 (183.00, 189.00) 75th Percentile 2 75
6 1 183.0 (181.00, 183.00) 50th Percentile 2 50
7 1 142.0 (70.00, 181.00) 25th Percentile 2 25
8 2 188.0 (167.00, NE) 75th Percentile 2 75
9 2 167.0 (63.00, 188.00) 50th Percentile 2 50
10 2 44.0 (22.00, 69.00) 25th Percentile 2 25
11 1 100.0 (100.00, 100.00) 0 (days) 3 1
12 1 90.8 (84.28, 97.32) 50 (days) 3 50
13 1 83.2 (74.43, 91.93) 100 (days) 3 100
14 1 72.3 (61.22, 83.44) 150 (days) 3 150
15 2 100.0 (100.00, 100.00) 0 (days) 3 1
16 2 73.7 (61.76, 85.71) 50 (days) 3 50
17 2 52.4 (32.61, 72.21) 100 (days) 3 100
18 2 52.4 (32.61, 72.21) 150 (days) 3 150

/========================================================= Transpose the dataset such that treatments become columns =========================================================/

In [76]:
proc sort data=summary100;
   by group order label trt;
run;
 
proc transpose data=summary100 out=t_summary100 prefix=trt;
   by group order label;
   var cp;
   id trt;
run;

proc print data=t_summary100; run;
SAS Output

The SAS System

Obs GROUP ORDER LABEL _NAME_ TRT1 TRT2
1 1 1 Event (Progression or Death) CP 57 ( 66.3%) 23 ( 27.4%)
2 1 2 Censored CP 29 ( 33.7%) 61 ( 72.6%)
3 2 25 25th Percentile CP 142.0 (70.00, 181.00) 44.0 (22.00, 69.00)
4 2 50 50th Percentile CP 183.0 (181.00, 183.00) 167.0 (63.00, 188.00)
5 2 75 75th Percentile CP 184.0 (183.00, 189.00) 188.0 (167.00, NE)
6 3 1 0 (days) CP 100.0 (100.00, 100.00) 100.0 (100.00, 100.00)
7 3 50 50 (days) CP 90.8 (84.28, 97.32) 73.7 (61.76, 85.71)
8 3 100 100 (days) CP 83.2 (74.43, 91.93) 52.4 (32.61, 72.21)
9 3 150 150 (days) CP 72.3 (61.22, 83.44) 52.4 (32.61, 72.21)

Create Quartiles Group Label¶

  • Add a clear section header for the quartiles block (e.g., Quartiles — Estimate (95% CI))
  • Use this label to visually separate quartiles from censoring and KM time-point estimates in the final table
In [84]:
data rowlabels;
   length label $200;
   label="Censor Summary - n (%)"; group=1;  order=0;  output; 
   label="Survival Summary - Estimate (95% CI)";group=2; order=0; output;
   label="Percent Survival - Estimate (95% CI)"; group=3; order=0; output;
run;

proc print data =rowlabels ; run;
SAS Output

The SAS System

Obs LABEL GROUP ORDER
1 Censor Summary - n (%) 1 0
2 Survival Summary - Estimate (95% CI) 2 0
3 Percent Survival - Estimate (95% CI) 3 0

/========================================================= Combine group labels to actual data =========================================================/

In [85]:
data final;
   set t_summary100 rowlabels hazard101(keep=group label order hazard);
   /*if order ne 0 then label="(*ESC*)\li250 "||strip(label);*/
run;
 
proc sort data=final;
   by group order label;
run;
 
proc print data=final; run;
SAS Output

The SAS System

Obs GROUP ORDER LABEL _NAME_ TRT1 TRT2 HAZARD
1 1 0 Censor Summary - n (%)        
2 1 1 Event (Progression or Death) CP 57 ( 66.3%) 23 ( 27.4%)  
3 1 2 Censored CP 29 ( 33.7%) 61 ( 72.6%)  
4 2 0 Survival Summary - Estimate (95% CI)        
5 2 25 25th Percentile CP 142.0 (70.00, 181.00) 44.0 (22.00, 69.00)  
6 2 50 50th Percentile CP 183.0 (181.00, 183.00) 167.0 (63.00, 188.00)  
7 2 75 75th Percentile CP 184.0 (183.00, 189.00) 188.0 (167.00, NE)  
8 3 0 Percent Survival - Estimate (95% CI)        
9 3 1 0 (days) CP 100.0 (100.00, 100.00) 100.0 (100.00, 100.00)  
10 3 50 50 (days) CP 90.8 (84.28, 97.32) 73.7 (61.76, 85.71)  
11 3 100 100 (days) CP 83.2 (74.43, 91.93) 52.4 (32.61, 72.21)  
12 3 150 150 (days) CP 72.3 (61.22, 83.44) 52.4 (32.61, 72.21)  
13 4 1 Hazard Ratio - Estimate (95% CI)       4.9 (3.1,7.8)

Report Generation¶

In [86]:
ods escapechar='^';

ods _all_ close;
ods listing close;

ods html5 (id=nb) options(bitmap_mode='inline') style=htmlblue;

ods text="^S={just=l font_face=Arial font_size=18pt font_weight=bold}Overall Survival by Treatment Group";
ods text="^S={just=l font_face=Arial font_size=11pt font_style=italic}Safety Analysis Set";
ods text="^S={just=l} ";

proc report data=final nowd headline headskip missing
  style(report)=[just=l outputwidth=95%]   /* <-- key change: no just=center */
  style(header)=[font_weight=bold];

  columns group order label trt1 trt2 hazard;

  define group / order noprint;
  define order / order noprint;

  define label / order ""
                 style(header)=[just=l]
                 style(column)=[just=l cellwidth=2.5in protectspecialchars=off];

  define trt1 / "Treatment A" "(N=%cmpres(&n1))"
                 style(column)=[cellwidth=1.2in just=c];

  define trt2 / "Treatment B" "(N=%cmpres(&n2))"
                 style(column)=[cellwidth=0.7in just=c];

  define hazard / "Treatment Difference"
                 style(column)=[cellwidth=0.7in just=c];

  compute after group;
    line @1 " ";
  endcomp;
run;

ods html5 (id=nb) close;
ods listing;
SAS Output SAS Output

The SAS System

Overall Survival by Treatment Group

Safety Analysis Set

  Treatment A
(N=86)
Treatment B
(N=84)
Treatment Difference
Censor Summary - n (%)      
Event (Progression or Death) 57 ( 66.3%) 23 ( 27.4%)  
Censored 29 ( 33.7%) 61 ( 72.6%)  
 
Survival Summary - Estimate (95% CI)      
25th Percentile 142.0 (70.00, 181.00) 44.0 (22.00, 69.00)  
50th Percentile 183.0 (181.00, 183.00) 167.0 (63.00, 188.00)  
75th Percentile 184.0 (183.00, 189.00) 188.0 (167.00, NE)  
 
Percent Survival - Estimate (95% CI)      
0 (days) 100.0 (100.00, 100.00) 100.0 (100.00, 100.00)  
50 (days) 90.8 (84.28, 97.32) 73.7 (61.76, 85.71)  
100 (days) 83.2 (74.43, 91.93) 52.4 (32.61, 72.21)  
150 (days) 72.3 (61.22, 83.44) 52.4 (32.61, 72.21)  
 
Hazard Ratio - Estimate (95% CI)     4.9 (3.1,7.8)
 
In [ ]:

In [ ]: