Frailty survival — R cross-check (parfm, coxme)¶
Companion to frailty_python.ipynb / frailty_pymc.ipynb. survreg has no frailty term, so we use:
parfm— parametric Weibull with log-normal frailty: the exact match to our model.coxme— Cox model with a Gaussian random intercept(1|id): the semiparametric frailty.
In [1]:
ok <- suppressWarnings(suppressMessages(require(parfm)))
if (!ok) { install.packages('parfm', repos='https://cloud.r-project.org'); library(parfm) }
d <- read.csv('kidney.csv'); d$disease <- relevel(factor(d$disease), ref='Other')
d$female <- as.integer(d$sex==2); d$age_c <- d$age - mean(d$age)
fit <- parfm(Surv(time, status) ~ age_c + female + disease, cluster='id', data=d,
dist='weibull', frailty='lognormal')
print(fit)
cat(sprintf('\nWeibull shape (rho) = %.3f frailty sd = sqrt(sigma2) = %.3f\n',
fit['rho','ESTIMATE'], sqrt(fit['sigma2','ESTIMATE'])))
cat(sprintf('female HR = %.2f PKD HR = %.2f\n', exp(fit['female','ESTIMATE']), exp(fit['diseasePKD','ESTIMATE'])))
Frailty distribution: lognormal
Baseline hazard distribution: Weibull
Loglikelihood: -329.885
ESTIMATE SE p-val
sigma2 0.315 0.325
rho 1.162 0.153
lambda 0.015 0.010
age_c 0.003 0.014 0.825
female -1.877 0.473 <.001 ***
diseaseAN 0.606 0.489 0.216
diseaseGN 0.121 0.492 0.806
diseasePKD -1.142 0.763 0.134
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Kendall's Tau: 0.13
Weibull shape (rho) = 1.162 frailty sd = sqrt(sigma2) = 0.562
female HR = 0.15 PKD HR = 0.32
In [2]:
ok2 <- suppressWarnings(suppressMessages(require(coxme)))
if (!ok2) { install.packages('coxme', repos='https://cloud.r-project.org'); library(coxme) }
cm <- coxme(Surv(time, status) ~ age_c + female + disease + (1|id), data=d)
print(cm)
cat(sprintf('\ncoxme: female HR = %.2f frailty sd = %.3f\n', exp(fixef(cm)['female']), sqrt(as.numeric(VarCorr(cm)$id))))
Cox mixed-effects model fit by maximum likelihood
Data: d
events, n = 58, 76
Iterations= 5 28
NULL Integrated Fitted
Log-likelihood -187.9028 -179.0217 -173.7424
Chisq df p AIC BIC
Integrated loglik 17.76 6.00 0.00685470 5.76 -6.60
Penalized loglik 28.32 9.11 0.00090634 10.10 -8.66
Model: Surv(time, status) ~ age_c + female + disease + (1 | id)
Fixed coefficients
coef exp(coef) se(coef) z p
age_c 0.003755551 1.0037626 0.01206409 0.31 0.76000
female -1.552913501 0.2116305 0.39038849 -3.98 0.00007
diseaseAN 0.358074826 1.4305727 0.43752739 0.82 0.41000
diseaseGN 0.119156978 1.1265467 0.44126751 0.27 0.79000
diseasePKD -1.329715978 0.2645524 0.69630860 -1.91 0.05600
Random effects
Group Variable Std Dev Variance
id Intercept 0.3428171 0.1175235
coxme: female HR = 0.21 frailty sd = 0.343
Results¶
parfm(parametric Weibull, log-normal frailty) is the direct match and confirms the Bayesian fits: shape ≈ 1.16, female HR ≈ 0.15, PKD HR ≈ 0.32, frailty sd ≈ 0.56 — right alongside from-scratch (0.65) and PyMC (0.76).coxme(semiparametric Cox frailty) gives the same covariate story (female HR ≈ 0.21) with a smaller frailty sd ≈ 0.34 — the Cox baseline absorbs some structure the Weibull attributes to the frailty.- The frailty is robustly non-zero across parfm, coxme, and both Bayesian engines (sd ≈ 0.3–0.8). Only a Cox-frailty boundary ML fit (
coxph(... + frailty(id))) collapses it to ~0 — the usual variance-component-at-the-boundary artefact with few small clusters. - Engines agree: from-scratch ≈ PyMC ≈
parfmon the Weibull frailty model;coxmethe semiparametric cross-check.