This file contains R code for the analyses in Chapter 7 of the book Dynamic Prediction in Clinical Survival Analysis (CRC Chapman & Hall) by Hans C. van Houwelingen and Hein Putter
R code written by Hein Putter (H.Putter@lumc.nl for comments/questions) The dynpred package is available from CRAN
Consistency with the book has been checked with - R version 2.14.0 - survival version 2.36-10 - dynpred version 0.1.1
Code
require(dynpred)################################################################################################################################################################# Section 7.1 based on large simulated data set##############################################################################################################################################################S1 <-function(t) exp(-(t/5)^(4/3))S2 <-function(t) exp(-(t/5)^(3/4))set.seed(20100629)n <-50000n2 <- n/2d1 <-data.frame(id=1:n2,time=5*(-log(runif(n2)))^(3/4),status=1,group=1)d2 <-data.frame(id=n2+(1:n2),time=5*(-log(runif(n2)))^(4/3),status=1,group=2)d <-rbind(d1,d2)d$status[d$time>10] <-0d$time[d$time>10] <-10# Simple Coxc1 <-coxph(Surv(time,status)~group, data=d)c1sf <-survfit(c1,newdata=data.frame(group=1:2))################################################################################################################################################################# Figure 7.1: The survival curves of the example and simple Cox model fit##############################################################################################################################################################tseq <-seq(0,10,by=0.05)# Actual figure with stepfunction for Cox fitsplot(tseq,S1(tseq),type="l",lwd=2,ylim=c(0,1),xlab="Time (years)",ylab="Survival")lines(tseq,S2(tseq),type="l",lwd=2,lty=2)lines(c(0,sf[1]$time),c(1,sf[1]$surv),type="l",lwd=2,col=8)lines(c(0,sf[2]$time),c(1,sf[2]$surv),type="l",lwd=2,col=8,lty=2)legend("topright",c("True group 1","True group 2","Cox fit group 1","Cox fit group 2"),lwd=2,col=c(1,1,8,8),lty=c(1,2,1,2),bty="n")################################################################################################################################################################# Figure 7.2: Survival predictions based on ?stopped Cox models?### for each time-point##############################################################################################################################################################tseq <-seq(0,10,by=0.05)nseq <-length(tseq)## Cox stoppedstoppedCox <-matrix(NA,nseq,2)stoppedCox[1,] <-c(1,1)# This is to monitor progressm <-floor(log10(nseq)) +1pre <-rep("\b", 2* m +1)for (i in2:length(tseq)) {cat(pre, i, "/", nseq, sep =""); flush.console() thor <- tseq[i] dhor <- d dhor$status[dhor$time>thor] <-0 dhor$time[dhor$time>thor] <- thor chor <-coxph(Surv(time,status)~group, data=dhor) sfhor <-survfit(chor,newdata=data.frame(group=1:2)) nhor <-length(sfhor[1]$time) stoppedCox[i,] <- sfhor$surv[nhor,]}plot(tseq,S1(tseq),type="l",lwd=2,ylim=c(0,1),xlab="Time (years)",ylab="Survival")lines(tseq,S2(tseq),type="l",lwd=2,lty=2)lines(tseq,stoppedCox[,1],type="l",lwd=2,col=8)lines(tseq,stoppedCox[,2],type="l",lwd=2,col=8,lty=2)legend("topright",c("True group 1","True group 2","Stopped Cox group 1","Stopped Cox group 2"),lwd=2,col=c(1,1,8,8),lty=c(1,2,1,2),bty="n")################################################################################################################################################################# Figure 7.3 Dynamic predictions with a window of 3 years comparing### a simple Cox model with the true model for the simulation example### of Section 7.1##############################################################################################################################################################w <-3tseq <-seq(0,7,by=0.05)Fw1 <-data.frame(time=tseq,Fw=1-exp(-(((tseq+3)/5)^(4/3) - (tseq/5)^(4/3))))Fw2 <-data.frame(time=tseq,Fw=1-exp(-(((tseq+3)/5)^(3/4) - (tseq/5)^(3/4))))## Simple Coxdcox <-coxph(Surv(time, status) ~ group, data=d)ndata <-data.frame(group=1:2)sf1 <-survfit(dcox, newdata=ndata[1,,drop=FALSE])sf2 <-survfit(dcox, newdata=ndata[2,,drop=FALSE])nseq <-length(tseq)Fwcox1 <- Fwcox2 <-data.frame(time=tseq,Fw=NA)# For loop takes a while; this is to monitor progressm <-floor(log10(nseq)) +1pre <-rep("\b", 2* m +1)for (i in1:nseq) {cat(pre, i, "/", nseq, sep =""); flush.console() ti <- tseq[i] S1 <-evalstep(sf1$time,sf1$surv,c(ti,ti+w),subst=1) S2 <-evalstep(sf2$time,sf2$surv,c(ti,ti+w),subst=1) Fwcox1$Fw[i] <-1- S1[2]/S1[1] Fwcox2$Fw[i] <-1- S2[2]/S2[1]}plot(Fw1$time,Fw1$Fw,type="l",lwd=2,xlim=c(0,7),ylim=c(0,1),xlab="Time (years)",ylab="Death probability")lines(Fw2$time,Fw2$Fw,type="l",lwd=2,col=8)lines(Fwcox1$time,Fwcox1$Fw,type="l",lwd=1,lty=2)lines(Fwcox2$time,Fwcox2$Fw,type="l",lwd=1,lty=2,col=8)legend("topright",c("True group 1","True group 2","Simple Cox group 1","Simple Cox group 2"),lwd=2,col=c(1,8,1,8),lty=c(1,1,2,2),bty="n")################################################################################################################################################################# Section 7.2 based on D1/D2 and ovarian data### D1/D2 data are NOT publicly available, code is available upon request### This document only contains the ovarian cancer analysis##############################################################################################################################################################data(ova)# Recall from Chapter 3cfull <-coxph(Surv(tyears, d) ~ Karn + Broders + FIGO + Ascites + Diam,data = ova)cfullXbeta <- ova$Xbeta <- cfull$linear.predictorsmean(ova$Xbeta)sqrt(var(ova$Xbeta))cfull <-coxph(Surv(tyears, d) ~ Xbeta, data = ova)w <-3tt <- ova$tyears[ova$d==1]tt <-sort(unique(c(0,tt,tt-w)))tt <- tt[tt>=0]nt <-length(tt)LMprobs <-data.frame(LM=tt,logHR=NA,lower=NA,upper=NA)for (i in1:nt) { LMdata <-cutLM(data=ova,outcome=list(time="tyears",status="d"),LM=tt[i],horizon=tt[i]+w,covs=list(fixed="Xbeta",varying=NULL)) LMcox <-coxph(Surv(tyears,d) ~ Xbeta, data=LMdata, method="breslow") se <-as.numeric(sqrt(LMcox$var)) LMprobs$logHR[i] <- LMcox$coef LMprobs$lower[i] <- LMcox$coef -qnorm(0.975)*se LMprobs$upper[i] <- LMcox$coef +qnorm(0.975)*se}################################################################################################################################################################# Table 7.2: Sliding landmark model for the ovarian cancer data############################################################################################################################################################### Supermodel based on stacked data setsLMs <-seq(0,5,by=0.10)LMdata <-cutLM(data=ova,outcome=list(time="tyears",status="d"),LM=0,horizon=w,covs=list(fixed=c("id","Xbeta"),varying=NULL))for (i in2:length(LMs)) LMdata <-rbind(LMdata,cutLM(data=ova,outcome=list(time="tyears",status="d"),LM=LMs[i],horizon=LMs[i]+w,covs=list(fixed=c("id","Xbeta"),varying=NULL)))f1 <-function(t) 1f2 <-function(t) (t/5)f3 <-function(t) (t/5)^2# Explicitly code interactions of treatment with LMLMdata$Xbeta1 <- LMdata$Xbeta*f1(LMdata$LM)LMdata$Xbeta2 <- LMdata$Xbeta*f2(LMdata$LM)LMdata$Xbeta3 <- LMdata$Xbeta*f3(LMdata$LM)# Supermodel based only on randomizationLMsupercox <-coxph(Surv(LM,tyears,d) ~ Xbeta1 + Xbeta2 + Xbeta3 +strata(LM)+cluster(id), data=LMdata, method="breslow")LMsupercoxbet <- LMsupercox$coefsig <- LMsupercox$var# Wald test to assess statistical signifiance of Xbeta x landmark interactionswh <-2:3wald <-t(bet[wh]) %*%solve(sig[wh,wh]) %*% bet[wh]pval <-1-pchisq(wald,df=length(wh))print(data.frame(wald=wald,pval=pval))################################################################################################################################################################# Figure 7.9: Sliding landmark effects and pointwise 95%-confidence intervals### for the prognostic index of Table 3.1 in the ovarian cancer data using### a window of width w = 3##############################################################################################################################################################m <-matrix(c(rep(1,length(LMs)),f2(LMs),f3(LMs)),length(LMs),3)LMsmooth <-data.frame(LM=LMs,logHR=as.numeric(m %*% bet))se <-sqrt(diag(m %*% sig %*%t(m)))LMsmooth$lower <- LMsmooth$logHR -qnorm(0.975)*seLMsmooth$upper <- LMsmooth$logHR +qnorm(0.975)*seLMprobs <- LMprobs[LMprobs$LM<=5,]LMsmooth <- LMsmooth[LMsmooth$LM<=5,]plot(LMprobs$LM,LMprobs$logHR,type="s",lwd=2,xlim=c(0,5),ylim=range(LMprobs[,2:4]),xlab="Time (years)",ylab="Log hazard ratio")lines(LMprobs$LM,LMprobs$lower,type="s")lines(LMprobs$LM,LMprobs$upper,type="s")lines(LMsmooth$LM,LMsmooth$logHR,type="l",lwd=2,lty=2)lines(LMsmooth$LM,LMsmooth$lower,type="l",lty=2)lines(LMsmooth$LM,LMsmooth$upper,type="l",lty=2)legend("bottomleft",c("Crude","Supermodel"),lwd=2,lty=1:2,bty="n")## ipl* modelg1 <-function(t) f2(t)g2 <-function(t) f3(t)LMdata$LM1 <-g1(LMdata$LM)LMdata$LM2 <-g2(LMdata$LM)LMsupercox2 <-coxph(Surv(LM,tyears,d) ~ Xbeta1 + Xbeta2 + Xbeta3 + LM1 + LM2+cluster(id), data=LMdata, method="breslow")LMsupercox2bet <- LMsupercox2$coefsig <- LMsupercox2$var# Wald test to assess statistical signifiance of Xbeta x landmark interactionswh <-2:3wald <-t(bet[wh]) %*%solve(sig[wh,wh]) %*% bet[wh]pval <-1-pchisq(wald,df=length(wh))print(data.frame(wald=wald,pval=pval))################################################################################################################################################################# Figure 7.10: Three year dynamic probabilities of dying based on the### proportional baselines landmark supermodel for the ovarian cancer data##############################################################################################################################################################ndata <-data.frame(Xbeta1=0,Xbeta2=0,Xbeta3=0,LM1=0,LM2=0)sf2 <-survfit(LMsupercox2, newdata=ndata)# Predictiontt <- tt[tt<=5]## Custom-made function to calculate predictionsFwpredict <-function(bet, sf, newdata, tt){ nt <-length(tt) Xbeta <- newdata$Xbeta sf <-data.frame(time=sf$time,surv=sf$surv,Haz=-log(sf$surv)) Fw <-data.frame(time=tt,Fw=NA)for (i in1:nt) { sfi <- sf tti <- tt[i] sfi$Haz <- sfi$Haz *exp(Xbeta*bet[1]*f1(tt[i]) + Xbeta*bet[2]*f2(tt[i])+ Xbeta*bet[3]*f3(tt[i]) + bet[4]*g1(tt[i]) + bet[5]*g2(tt[i])) tmp <-evalstep(sfi$time,sfi$Haz,c(tti,tti+w),subst=0) Fw$Fw[i] <-exp(-(tmp[2]-tmp[1])) }return(Fw)}# Mean - 2*sdndata <-data.frame(Xbeta =mean(ova$Xbeta) -2*sd(ova$Xbeta))Fw1 <-Fwpredict(LMsupercox2$coef, sf2, ndata, tt)# Mean - sdndata <-data.frame(Xbeta =mean(ova$Xbeta) -sd(ova$Xbeta))Fw2 <-Fwpredict(LMsupercox2$coef, sf2, ndata, tt)# Meanndata <-data.frame(Xbeta =mean(ova$Xbeta))Fw3 <-Fwpredict(LMsupercox2$coef, sf2, ndata, tt)# Mean + sdndata <-data.frame(Xbeta =mean(ova$Xbeta) +sd(ova$Xbeta))Fw4 <-Fwpredict(LMsupercox2$coef, sf2, ndata, tt)# Mean - 2*sdndata <-data.frame(Xbeta =mean(ova$Xbeta) +2*sd(ova$Xbeta))Fw5 <-Fwpredict(LMsupercox2$coef, sf2, ndata, tt)## Plot resultsplot(Fw3$time,1-Fw3$Fw,type="s",lwd=2,xlim=c(0,5),ylim=c(0,1),xlab="Time (years)",ylab="Probability of death")lines(Fw1$time,1-Fw1$Fw,type="s",lwd=2,lty=2)lines(Fw2$time,1-Fw2$Fw,type="s",lwd=2,lty=3)lines(Fw4$time,1-Fw4$Fw,type="s",lwd=2,lty=4)lines(Fw5$time,1-Fw5$Fw,type="s",lwd=2,lty=5)legend("topright",c("Mean+2sd","Mean+sd","Mean","Mean-sd","Mean-2sd"),lwd=2,lty=c(5,4,1,3,2),bty="n")