The logrank test compares groups using an integrated weighted difference in hazards, rather than using survival times. The weight is proportional to the information about the difference at time \(t\) under a proportional hazards assumption.
It’s possible to construct two distributions where the survival curve for group 1 lies completely above the survival curve for group 2, but the hazard rate curves cross. It’s then clear which group is better for survival, but there’s a question as to whether the logrank test will agree
h1<-function(t) ifelse(t<5, .1,.5)
h2<-function(t) ifelse(t<5, .5, .1)
H1<-function(t) ifelse(t<5, t*.1, .5*(t-5)+.1*5)
H2<-function(t) ifelse(t<5, t*.5, .1*(t-5)+.5*5)
S1<-function(t) exp(-H1(t))
S2<-function(t) exp(-H2(t))
curve(h1(x),from=0 ,to=10,xlab="time",ylab="hazard")
curve(h2(x),add=TRUE,col="red")
curve(S1(x),from=0, to=10,xlab="time",ylab="Proportion surviving",ylim=c(0,1))
curve(S2(x),add=TRUE,col="red")
Under right censoring, it’s fairly clear that the information is strictly decreasing in \(t\), so you’d expect the logrank test to agree with the ordering of survival curves. It does; there’s a straightforward proof using integration by parts in the book by Fleming & Harrington.
With left truncation (late entry) things are much less clear. I learned about this possibility from Scott Emerson.
We can generate data and independent left-truncation times, and look at the Kaplan-Meier estimators both with and without the left truncation:
library(survival)
## Warning: package 'survival' was built under R version 3.3.2
set.seed(2017-9-8)
times<-c(seq(0,29,length=1000),10*(3:10))
u1<-runif(1000)
t1<-sapply(u1, function(u) min(times[S1(times)<u]))
u2<-runif(1000)
t2<-sapply(u2, function(u) min(times[S2(times)<u]))
tt<-c(t1,t2)
ss<-rep(1,length(tt))
g<-rep(0:1,each=1000)
start<-runif(2000,0,5)+runif(2000,0,5)
d<-data.frame(start=start,time=tt,status=ss,group=g)
plot(survfit(Surv(time,status)~group, data=d), col=1:2)
plot(survfit(Surv(start,time,status)~group, data=subset(d, start<time)), col=1:2)
It’s clear that group 1 (black) is the group you want to be in.
Now, the survival
package doesn’t do the logrank test with left-truncated data, but it does do the score test for the Cox model, which is the same thing
First, without left truncation
summary(coxph(Surv(time,status)~group,data=d))
## Call:
## coxph(formula = Surv(time, status) ~ group, data = d)
##
## n= 2000, number of events= 2000
##
## coef exp(coef) se(coef) z Pr(>|z|)
## group 0.82942 2.29199 0.04737 17.51 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## exp(coef) exp(-coef) lower .95 upper .95
## group 2.292 0.4363 2.089 2.515
##
## Concordance= 0.651 (se = 0.006 )
## Rsquare= 0.139 (max possible= 1 )
## Likelihood ratio test= 299.2 on 1 df, p=0
## Wald test = 306.6 on 1 df, p=0
## Score (logrank) test = 320.4 on 1 df, p=0
Now, with:
summary(coxph(Surv(start, time,status)~group,data=subset(d,start<time)))
## Call:
## coxph(formula = Surv(start, time, status) ~ group, data = subset(d,
## start < time))
##
## n= 701, number of events= 701
##
## coef exp(coef) se(coef) z Pr(>|z|)
## group -0.5858 0.5566 0.1084 -5.405 6.49e-08 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## exp(coef) exp(-coef) lower .95 upper .95
## group 0.5566 1.796 0.4501 0.6884
##
## Concordance= 0.53 (se = 0.008 )
## Rsquare= 0.045 (max possible= 1 )
## Likelihood ratio test= 32.41 on 1 df, p=1.252e-08
## Wald test = 29.21 on 1 df, p=6.486e-08
## Score (logrank) test = 29.79 on 1 df, p=4.805e-08
The direction of the difference has reversed according to both the logrank test and the hazard ratio.
Like all rank tests, the logrank test (and the Cox model) are not transitive. What’s special about left truncation is that the logrank test (and the Cox model) aren’t transitive even when the distributions are stochastically ordered.