6.4 The log-rank test in software
Software 6.4.1 (The log-rank test by hand). Example 6.1.1: a loop over the death times accumulating observed and expected deaths in group \(1\) and the hypergeometric variance; then survdiff:
> library(survival)
> o <- ovarian # 26 patients, two treatments
> tt <- sort(unique(o$futime[o$fustat == 1])) # distinct death times
> O1 <- E1 <- V <- 0
> for (t in tt) {
+ n <- sum(o$futime >= t); n1 <- sum(o$futime >= t & o$rx == 1)
+ d <- sum(o$futime == t & o$fustat == 1)
+ d1 <- sum(o$futime == t & o$fustat == 1 & o$rx == 1)
+ O1 <- O1 + d1; E1 <- E1 + d * n1 / n
+ if (n > 1) V <- V + d * (n1 / n) * (1 - n1 / n) * (n - d) / (n - 1)
+ }
> round(c(O1 = O1, E1 = E1, V = V, chisq = (O1 - E1)^2 / V), 4)
O1 E1 V chisq
7.0000 5.2335 2.9362 1.0627
> survdiff(Surv(futime, fustat) ~ rx, data = o)
Call:
survdiff(formula = Surv(futime, fustat) ~ rx, data = o)
N Observed Expected (O-E)^2/E (O-E)^2/V
rx=1 13 7 5.23 0.596 1.06
rx=2 13 5 6.77 0.461 1.06
Chisq= 1.1 on 1 degrees of freedom, p= 0.3
Reading the output. The hand calculation gives \(O_1 = 7\), \(E_1 = 5.23\), \(V = 2.94\) and \(\chi ^2 = 1.06\), the value in the last column of survdiff’s table, with \(p = 0.3\). The column \((O - E)^2/E\) is an older approximation that is smaller and should not be used.
Python: lifelines.statistics.logrank_test(t1, t2, e1, e2).
Software 6.4.2 (Effects, weights and strata). The log-rank test for sex in lung with its \(p\)-value and the observed-to-expected hazard ratio; the Peto–Peto version; then the veteran regimens unstratified, stratified by cell type, and by the Peto–Peto test:
> sd <- survdiff(Surv(time, status) ~ sex, data = lung) > round(c(chisq = sd$chisq, p = pchisq(sd$chisq, 1, lower.tail = FALSE)), 5) chisq p 10.32674 0.00131 > hr <- (sd$obs[2] / sd$exp[2]) / (sd$obs[1] / sd$exp[1]); round(hr, 3) [1] 0.59 > pp <- survdiff(Surv(time, status) ~ sex, data = lung, rho = 1) # Peto-Peto > round(pp$chisq, 3) [1] 12.714 > v1 <- survdiff(Surv(time, status) ~ trt, data = veteran) > v2 <- survdiff(Surv(time, status) ~ trt + strata(celltype), data = veteran) > round(c(unstratified = v1$chisq, stratified = v2$chisq), 3) unstratified stratified 0.008 0.702 > v3 <- survdiff(Surv(time, status) ~ trt, data = veteran, rho = 1) > round(c(peto_peto = v3$chisq, + p = pchisq(v3$chisq, 1, lower.tail = FALSE)), 3) peto_peto p 0.871 0.351
Reading the output. For sex, \(\chi ^2 = 10.3\) (\(p = 0.0013\)) and \(\widehat {\text {HR}} = 0.59\); the Peto–Peto statistic is larger, \(12.7\), because the curves separate early. For the veteran regimens, none of the three tests finds a difference (\(p = 0.93\), \(0.40\), \(0.35\)).
Python: logrank_test(..., weightings=’peto’).
Software 6.4.3 (Three groups and a trend). Example 6.2.1: the numbers, observed and expected deaths for ECOG scores \(0\), \(1\), \(2\); the \(k\)-group test; and the trend test with scores \(0, 1, 2\) using the covariance matrix that survdiff returns:
> lg <- subset(lung, ph.ecog %in% 0:2) > e3 <- survdiff(Surv(time, status) ~ ph.ecog, data = lg) > round(cbind(n = e3$n, O = e3$obs, E = e3$exp), 2) n O E ph.ecog=0 63 37 53.90 ph.ecog=1 113 82 83.09 ph.ecog=2 50 44 26.00 > round(c(chisq = e3$chisq, p = pchisq(e3$chisq, 2, lower.tail = FALSE)), 6) chisq p 18.012097 0.000123 > w <- 0:2; U <- sum(w * (e3$obs - e3$exp)) # trend test, scores 0, 1, 2 > trend <- U^2 / c(t(w) %*% e3$var %*% w) > round(c(trend = trend, p = pchisq(trend, 1, lower.tail = FALSE)), 6) trend p 16.44654 0.00005
Reading the output. Patients with score \(2\) had \(44\) deaths against \(26\) expected, those with score \(0\) had \(37\) against \(54\). The overall test gives \(\chi ^2_2 = 18.0\); the trend test \(\chi ^2_1 = 16.4\) captures almost all of it with one degree of freedom.
Python: lifelines.statistics.multivariate_logrank_test.
Questions on this section
Stuck on something here? Ask below and it stays attached to this topic.