Interval-censored survival — R cross-check (survreg, icenReg)¶
Companion to icsurv_python.ipynb / icsurv_pymc.ipynb. survival::survreg fits a parametric Weibull with Surv(l, u, type='interval2'); icenReg::ic_par is the dedicated interval-censored parametric tool.
In [1]:
suppressMessages(library(survival))
d <- read.csv('mice_tumor_ic.csv')
d$u2 <- ifelse(is.infinite(d$u) | d$u > 1e8, NA, d$u) # NA upper = right-censored
fit <- survreg(Surv(l, u2, type='interval2') ~ grp, data=d, dist='weibull')
k <- 1/fit$scale; hr <- exp(-coef(fit)[['grpge']]/fit$scale)
cat(sprintf('survreg : shape k = %.3f germ-free PH HR = %.2f\n', k, hr))
print(round(summary(fit)$table, 3))
survreg : shape k = 2.028 germ-free PH HR = 2.19
Value Std. Error z p (Intercept) 6.948 0.211 32.937 0.000 grpge -0.388 0.281 -1.379 0.168 Log(scale) -0.707 0.382 -1.852 0.064
In [2]:
ok <- suppressWarnings(suppressMessages(require(icenReg)))
if (!ok) { install.packages('icenReg', repos='https://cloud.r-project.org'); library(icenReg) }
fit2 <- ic_par(cbind(l, u) ~ grp, data=transform(read.csv('mice_tumor_ic.csv'), u=ifelse(is.infinite(u)|u>1e8, Inf, u)),
model='ph', dist='weibull')
cat('icenReg ic_par (Weibull PH):\n'); print(summary(fit2))
icenReg ic_par (Weibull PH):
Model: Cox PH
Dependency structure assumed: Independence
Baseline: weibull
Call: ic_par(formula = cbind(l, u) ~ grp, data = transform(read.csv("mice_tumor_ic.csv"),
u = ifelse(is.infinite(u) | u > 1e+08, Inf, u)), model = "ph",
dist = "weibull")
Estimate Exp(Est) Std.Error z-value p
log_shape 0.7072 2.028 0.3820 1.852 0.06410
log_scale 6.8190 915.000 0.1268 53.760 0.00000
grpge 0.7862 2.195 0.3359 2.341 0.01925
final llk = -80.3202
Iterations = 16
In [3]:
# Turnbull NPMLE -- nonparametric (KM-analog) survival for interval-censored data (rendered inline)
dd <- transform(read.csv('mice_tumor_ic.csv'), u = ifelse(is.infinite(u) | u > 1e8, Inf, u))
np <- ic_np(cbind(l, u) ~ grp, data = dd)
options(repr.plot.width = 7, repr.plot.height = 5)
plot(np, col = c('steelblue', 'firebrick'), lwd = 2, xlab = 'days', ylab = 'S(t)',
main = 'Turnbull NPMLE survival by group (interval-censored)')
legend('bottomleft', legend = c('conventional (ce)', 'germ-free (ge)'),
col = c('steelblue', 'firebrick'), lwd = 2, bty = 'n')
Results¶
survreg(interval2) gives k ≈ 2.03 and germ-free HR ≈ 2.2 — matching the from-scratch fit.icenReg::ic_par(the purpose-built interval-censored Weibull PH) gives the same hazard ratio.- Engines agree on the effect: from-scratch,
survreg, andicenRegall give germ-free HR ~2.2×, with PyMC's posterior-mean HR a touch higher (~2.5, averaging $e^\beta$ over a right-skewed posterior). The MLE shape is k ≈ 2.03, the Bayesian fits ~1.74. Estimated honestly from the left- and right-censored onset times via the same $S(l)-S(u)$ likelihood.