Nonparametric Bayesian Survival (R)¶

The survival package — Kaplan–Meier, Nelson–Aalen, Cox¶

The R counterpart to npsurv_python.ipynb. Where the Python notebook builds the nonparametric hazard from scratch (a Gamma-process piecewise-exponential Gibbs) and cross-checks in PyMC, this notebook uses the standard survival package: survfit for the Kaplan–Meier survival curve and Nelson–Aalen cumulative hazard (the classical nonparametric estimators), coxph for the proportional-hazards treatment effect, and basehaz for the nonparametric baseline hazard. These are the frequentist limit of the Gamma-process posterior — the priorless version of the same picture — on the Gehan leukemia trial.

In [1]:
options(repr.plot.width=12, repr.plot.height=4.4)
.libPaths(c("C:/Users/user/R/win-library/4.6", .libPaths())); suppressMessages(library(survival))
BLUE<-"#2b6cb0"; RED<-"#c53030"; GREEN<-"#2f855a"; GREY<-"#718096"
d<-read.csv("gehan.csv"); cat(nrow(d), "patients,", sum(d$status), "events; 6-MP", sum(d$treat==1), "control", sum(d$treat==0), "\n")
42 patients, 30 events; 6-MP 21 control 21 

1. Kaplan–Meier & Nelson–Aalen — the nonparametric estimators¶

survfit(Surv(time, status) ~ 1) gives the Kaplan–Meier survival curve (a product over event times) and, via type="fleming-harrington", the Nelson–Aalen cumulative hazard — the classical nonparametric estimators the Bayesian Gamma-process posterior mean reproduces.

In [2]:
km<-survfit(Surv(time,status)~1, data=d)
na<-survfit(Surv(time,status)~1, data=d, type="fleming-harrington")
par(mfrow=c(1,2), mar=c(4,4,3,1))
plot(km, conf.int=TRUE, col=c(BLUE,GREY,GREY), lwd=2, xlab="weeks", ylab="S(t)", main="Kaplan-Meier survival (survfit)")
legend("topright", c("KM estimate","95% CI"), col=c(BLUE,GREY), lwd=2, bty="n")
plot(na$time, -log(na$surv), type="s", col=GREEN, lwd=2, xlab="weeks", ylab="H(t)", main="Nelson-Aalen cumulative hazard")
par(mfrow=c(1,1))
cat(sprintf("median survival %.0f weeks; the KM/Nelson-Aalen estimates match the Bayesian nonparametric posterior mean\n", summary(km)$table["median"]))
cat("in npsurv_python.ipynb -- the prior adds a credible band and mild smoothing, and matters more with sparser data.\n")
median survival 12 weeks; the KM/Nelson-Aalen estimates match the Bayesian nonparametric posterior mean
in npsurv_python.ipynb -- the prior adds a credible band and mild smoothing, and matters more with sparser data.
No description has been provided for this image

2. Proportional hazards — coxph and the baseline hazard¶

coxph fits the semiparametric Cox model (baseline hazard left free); $e^{\hat\beta}$ is the treatment hazard ratio, survfit draws the two arms' survival, and basehaz returns the nonparametric (Breslow) baseline cumulative hazard.

In [3]:
cox<-coxph(Surv(time,status)~treat, data=d); hr<-exp(coef(cox)); ci<-exp(confint(cox))
cat(sprintf("Cox HR(6-MP) = %.2f  95%% CI [%.2f, %.2f]\n", hr, ci[1], ci[2]))
cat(sprintf("coxph applies the EFRON tie correction by default, and %d of %d event times here are tied,\n",
            sum(duplicated(d$time)), nrow(d)))
cat("so the convention is not cosmetic. The Python notebook now reports both: its Efron fit reproduces\n")
cat("this figure exactly, its Breslow fit gives 0.22. The engines never disagreed about the data.\n")
cat("(The Bayesian nonparametric PH posterior in Python gives HR ~0.19, well inside this interval.)\n")
sf<-survfit(cox, newdata=data.frame(treat=c(0,1)))
par(mfrow=c(1,2), mar=c(4,4,3,1))
plot(sf, col=c(RED,BLUE), lwd=2.3, xlab="weeks in remission", ylab="S(t)", main=sprintf("Survival by arm (Cox, HR=%.2f)", hr))
km2<-survfit(Surv(time,status)~treat, data=d); lines(km2, col=c(RED,BLUE), lty=3, lwd=1)
legend("topright", c("control","6-MP","Kaplan-Meier"), col=c(RED,BLUE,"black"), lwd=2, lty=c(1,1,3), bty="n")
bh<-basehaz(cox, centered=FALSE)
plot(bh$time, bh$hazard, type="s", col=GREEN, lwd=2, xlab="weeks", ylab="baseline cumulative hazard", main="Nonparametric baseline hazard (Breslow)")
par(mfrow=c(1,1))
cat("6-MP cuts the relapse hazard to about a fifth of placebo, matching the from-scratch Bayesian Gamma-process\n")
cat("and PyMC piecewise-exponential fits; coxph's Breslow baseline is the frequentist nonparametric hazard.\n")
Cox HR(6-MP) = 0.21  95% CI [0.09, 0.47]
coxph applies the EFRON tie correction by default, and 18 of 42 event times here are tied,
so the convention is not cosmetic. The Python notebook now reports both: its Efron fit reproduces
this figure exactly, its Breslow fit gives 0.22. The engines never disagreed about the data.
(The Bayesian nonparametric PH posterior in Python gives HR ~0.19, well inside this interval.)
6-MP cuts the relapse hazard to about a fifth of placebo, matching the from-scratch Bayesian Gamma-process
and PyMC piecewise-exponential fits; coxph's Breslow baseline is the frequentist nonparametric hazard.
No description has been provided for this image

3. Summary¶

The standard survival package reproduces the Python results by classical estimators rather than a Bayesian prior: survfit gives the Kaplan–Meier survival and Nelson–Aalen cumulative hazard that the Gamma-process posterior mean matches, coxph gives the treatment HR ≈ 0.2 for 6-mercaptopurine (matching the from-scratch and PyMC fits), and basehaz the nonparametric baseline hazard. These are exactly the priorless limit of the Bayesian nonparametric model in npsurv_python.ipynb.

The same Gehan/Leuk data runs through the survival arc as one progression — parametric Weibull (Bayesian Weibull Proportional-Hazards Model), semiparametric Cox (Bayesian Cox via Poisson), and the fully nonparametric hazard here. The final BNP notebook turns to Polya trees, a prior placed directly on the distribution.