11.5 Competing risks and multiple events in software
Software 11.5.1 (Cumulative incidence). In R’s survival package, a factor event whose first level is censoring makes survfit compute the Aalen–Johansen estimates. The mgus2 data, with the time to progression or to death (in months), the estimated probabilities at \(10\) and \(20\) years of being event-free, progressed or dead, and the incorrect one minus Kaplan–Meier for progression:
> library(survival); options(show.signif.stars = FALSE)
> m <- mgus2
> m$etime <- with(m, ifelse(pstat == 1, ptime, futime)) # months
> m$event <- factor(with(m, ifelse(pstat == 1, 1, 2 * death)), 0:2,
+ labels = c("censor", "pcm", "death"))
> table(m$event)
censor pcm death
409 115 860
> aj <- survfit(Surv(etime, event) ~ 1, data = m) # Aalen-Johansen
> s <- summary(aj, times = c(120, 240))
> round(cbind(months = s$time, s$pstate), 4)
months
[1,] 120 0.4045 0.0637 0.5318
[2,] 240 0.1762 0.0998 0.7240
> km <- survfit(Surv(etime, event == "pcm") ~ 1, data = m) # wrong: 1 - KM
> round(1 - summary(km, times = c(120, 240))$surv, 4)
[1] 0.0952 0.2096
Reading the output. At \(120\) months, \(40\%\) are alive without progression, \(6.4\%\) have progressed and \(53\%\) have died without progression; the rows add to one. The naive estimate of progression, \(9.5\%\), is too high.
Python: lifelines.AalenJohansenFitter, with event_of_interest set to the cause.
Software 11.5.2 (Cause-specific and Fine–Gray models). A cause-specific Cox model for progression, the Fine–Gray model fitted with finegray (which builds the weighted data set) and a weighted coxph, and the cause-specific model for death:
> cs <- coxph(Surv(etime, event == "pcm") ~ age + sex + mspike, data = m) > fg <- finegray(Surv(etime, event) ~ ., data = m, etype = "pcm") > sd <- coxph(Surv(fgstart, fgstop, fgstatus) ~ age + sex + mspike, + data = fg, weights = fgwt) > round(rbind(cause_specific = exp(coef(cs)), + subdistribution = exp(coef(sd))), 4) age sexM mspike cause_specific 1.0165 0.9950 2.4208 subdistribution 0.9833 0.8081 2.4312 > cd <- coxph(Surv(etime, event == "death") ~ age + sex + mspike, data = m) > round(exp(coef(cd)), 4) # cause-specific, for death age sexM mspike 1.0674 1.4754 0.9425
Reading the output. The effect of the spike is the same in both models; the age and sex effects differ, because age and sex act strongly on death, the competing risk.
Python: lifelines fits cause-specific models with CoxPHFitter; the Fine–Gray model is not in lifelines.
Software 11.5.3 (Frailty and robust standard errors). The kidney data with a marginal model and robust standard errors, and with a shared gamma frailty for each patient, with the estimated frailty variance and its test:
> k1 <- coxph(Surv(time, status) ~ age + sex + cluster(id), data = kidney)
> k2 <- coxph(Surv(time, status) ~ age + sex +
+ frailty(id, distribution = "gamma"), data = kidney,
+ control = coxph.control(iter.max = 100))
> round(rbind(marginal = exp(coef(k1)), frailty = exp(coef(k2))), 3)
age sex
marginal 1.002 0.436
frailty 1.005 0.204
> round(summary(k1)$coef["sex", c("robust se", "Pr(>|z|)")], 4)
robust se Pr(>|z|)
0.4835 0.0863
> round(summary(k2)$coefficients[1:2, c("se(coef)", "p")], 4)
se(coef) p
age 0.0119 0.6587
sex 0.4606 0.0006
> round(c(theta = k2$history[[1]]$theta, # frailty variance
+ p_frailty = summary(k2)$coefficients[3, "p"]), 4)
theta p_frailty
0.4078 0.0404
Reading the output. The frailty model’s sex effect is stronger (\(0.20\) against \(0.44\)) and more significant; \(\hat \theta = 0.41\) with \(p = 0.04\) shows the patients’ two infection times are dependent.
Python: CoxPHFitter with cluster_col gives robust errors; gamma frailty is not in lifelines.
Software 11.5.4 (Recurrent events). The cgd trial in counting-process form: the Andersen–Gill and PWP models with robust errors, the number of infections in each arm, and a model for the first infection only:
> ag <- coxph(Surv(tstart, tstop, status) ~ treat + cluster(id), data = cgd)
> pwp <- coxph(Surv(tstart, tstop, status) ~ treat + strata(enum) +
+ cluster(id), data = cgd)
> cols <- c("exp(coef)", "se(coef)", "robust se", "Pr(>|z|)")
> round(rbind(AG = summary(ag)$coef[, cols],
+ PWP = summary(pwp)$coef[, cols]), 4)
exp(coef) se(coef) robust se Pr(>|z|)
AG 0.3344 0.2610 0.3119 0.0004
PWP 0.4231 0.2802 0.2919 0.0032
> with(cgd, tapply(status, treat, sum)) # infections in each arm
placebo rIFN-g
56 20
> first <- coxph(Surv(tstart, tstop, status) ~ treat, data = cgd,
+ subset = enum == 1) # first infection only
> round(summary(first)$coef[, c("exp(coef)", "Pr(>|z|)")], 4)
exp(coef) Pr(>|z|)
0.3349 0.0011
Reading the output. All three analyses put the reduction in infections at \(58\%\) to \(67\%\); the robust standard error is about \(20\%\) larger than the naive one in the Andersen–Gill model.
Python: CoxTimeVaryingFitter with start–stop data in lifelines.
Questions on this section
Stuck on something here? Ask below and it stays attached to this topic.