Chapter 6 Repeated Events

# Cluster model 
bladder.rse <- coxph(Surv(start, stop, event == 1) ~ rx + number + size + cluster(id), data = bladder)
summary(bladder.rse)
## Call:
## coxph(formula = Surv(start, stop, event == 1) ~ rx + number + 
##     size, data = bladder, cluster = id)
## 
##   n= 178, number of events= 112 
## 
##            coef exp(coef) se(coef) robust se      z Pr(>|z|)   
## rx     -0.46469   0.62833  0.19973   0.26556 -1.750  0.08015 . 
## number  0.17496   1.19120  0.04707   0.06304  2.775  0.00551 **
## size   -0.04366   0.95728  0.06905   0.07762 -0.563  0.57376   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
##        exp(coef) exp(-coef) lower .95 upper .95
## rx        0.6283     1.5915    0.3734     1.057
## number    1.1912     0.8395    1.0527     1.348
## size      0.9573     1.0446    0.8222     1.115
## 
## Concordance= 0.634  (se = 0.032 )
## Likelihood ratio test= 17.52  on 3 df,   p=6e-04
## Wald test            = 11.54  on 3 df,   p=0.009
## Score (logrank) test = 19.52  on 3 df,   p=2e-04,   Robust = 11.27  p=0.01
## 
##   (Note: the likelihood ratio and score tests assume independence of
##      observations within a cluster, the Wald and robust score tests do not).
# Conditional Model - Assuming Variable Effects Same Across Strata - PWP model
bladder.con <- coxph(Surv(start, stop, event == 1) ~ rx + number + size + strata(enum)+cluster(id), data = bladder)
summary(bladder.con)
## Call:
## coxph(formula = Surv(start, stop, event == 1) ~ rx + number + 
##     size + strata(enum), data = bladder, cluster = id)
## 
##   n= 178, number of events= 112 
## 
##             coef exp(coef)  se(coef) robust se      z Pr(>|z|)  
## rx     -0.333489  0.716420  0.216168  0.204787 -1.628   0.1034  
## number  0.119617  1.127065  0.053338  0.051387  2.328   0.0199 *
## size   -0.008495  0.991541  0.072762  0.061635 -0.138   0.8904  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
##        exp(coef) exp(-coef) lower .95 upper .95
## rx        0.7164     1.3958    0.4796     1.070
## number    1.1271     0.8873    1.0191     1.246
## size      0.9915     1.0085    0.8787     1.119
## 
## Concordance= 0.616  (se = 0.032 )
## Likelihood ratio test= 6.51  on 3 df,   p=0.09
## Wald test            = 7.26  on 3 df,   p=0.06
## Score (logrank) test = 6.91  on 3 df,   p=0.07,   Robust = 8.83  p=0.03
## 
##   (Note: the likelihood ratio and score tests assume independence of
##      observations within a cluster, the Wald and robust score tests do not).
# Conditional Model - Assuming Variable Effects change Across Strata - PWP model
bladder.con <- coxph(Surv(start, stop, event == 1) ~ strata(enum)*rx + strata(enum)*number + strata(enum)*size + cluster(id), data = bladder)
summary(bladder.con)
## Call:
## coxph(formula = Surv(start, stop, event == 1) ~ strata(enum) + 
##     rx + number + size + strata(enum):rx + strata(enum):number + 
##     strata(enum):size, data = bladder, cluster = id)
## 
##   n= 178, number of events= 112 
## 
##                               coef exp(coef) se(coef) robust se      z Pr(>|z|)
## rx                        -0.52598   0.59097  0.31583   0.31524 -1.669  0.09521
## number                     0.23818   1.26894  0.07588   0.07459  3.193  0.00141
## size                       0.06961   1.07209  0.10156   0.08863  0.785  0.43220
## strata(enum)enum=2:rx      0.02215   1.02239  0.51451   0.60852  0.036  0.97097
## strata(enum)enum=3:rx      0.66664   1.94768  0.74348   0.57671  1.156  0.24771
## strata(enum)enum=4:rx      0.57632   1.77947  0.85238   0.62678  0.919  0.35784
## strata(enum)enum=2:number -0.26282   0.76888  0.11763   0.16532 -1.590  0.11189
## strata(enum)enum=3:number -0.18852   0.82819  0.20026   0.14196 -1.328  0.18420
## strata(enum)enum=4:number -0.03390   0.96667  0.25366   0.19351 -0.175  0.86092
## strata(enum)enum=2:size   -0.23033   0.79427  0.15910   0.17506 -1.316  0.18827
## strata(enum)enum=3:size    0.09849   1.10350  0.28757   0.18033  0.546  0.58497
## strata(enum)enum=4:size   -0.06052   0.94128  0.35382   0.37643 -0.161  0.87228
##                             
## rx                        . 
## number                    **
## size                        
## strata(enum)enum=2:rx       
## strata(enum)enum=3:rx       
## strata(enum)enum=4:rx       
## strata(enum)enum=2:number   
## strata(enum)enum=3:number   
## strata(enum)enum=4:number   
## strata(enum)enum=2:size     
## strata(enum)enum=3:size     
## strata(enum)enum=4:size     
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
##                           exp(coef) exp(-coef) lower .95 upper .95
## rx                           0.5910     1.6921    0.3186     1.096
## number                       1.2689     0.7881    1.0964     1.469
## size                         1.0721     0.9328    0.9011     1.275
## strata(enum)enum=2:rx        1.0224     0.9781    0.3102     3.370
## strata(enum)enum=3:rx        1.9477     0.5134    0.6290     6.031
## strata(enum)enum=4:rx        1.7795     0.5620    0.5209     6.079
## strata(enum)enum=2:number    0.7689     1.3006    0.5561     1.063
## strata(enum)enum=3:number    0.8282     1.2075    0.6270     1.094
## strata(enum)enum=4:number    0.9667     1.0345    0.6616     1.412
## strata(enum)enum=2:size      0.7943     1.2590    0.5636     1.119
## strata(enum)enum=3:size      1.1035     0.9062    0.7750     1.571
## strata(enum)enum=4:size      0.9413     1.0624    0.4501     1.969
## 
## Concordance= 0.629  (se = 0.036 )
## Likelihood ratio test= 14.5  on 12 df,   p=0.3
## Wald test            = 20.29  on 12 df,   p=0.06
## Score (logrank) test = 15.73  on 12 df,   p=0.2,   Robust = 15.8  p=0.2
## 
##   (Note: the likelihood ratio and score tests assume independence of
##      observations within a cluster, the Wald and robust score tests do not).
# Gap Time Model - Assuming Variable Effects Same Across Strata #
# Can easily extend the gap time model to assume variable effects different same as above. #
bladder.gap <- coxph(Surv(time = (stop - start), event == 1) ~ rx + number + size + strata(enum) +cluster(id), data = bladder)
summary(bladder.gap)
## Call:
## coxph(formula = Surv(time = (stop - start), event == 1) ~ rx + 
##     number + size + strata(enum), data = bladder, cluster = id)
## 
##   n= 178, number of events= 112 
## 
##             coef exp(coef)  se(coef) robust se      z Pr(>|z|)   
## rx     -0.279005  0.756536  0.207348  0.215624 -1.294  0.19569   
## number  0.158046  1.171220  0.051942  0.050940  3.103  0.00192 **
## size    0.007415  1.007443  0.070023  0.064333  0.115  0.90824   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
##        exp(coef) exp(-coef) lower .95 upper .95
## rx        0.7565     1.3218    0.4958     1.154
## number    1.1712     0.8538    1.0599     1.294
## size      1.0074     0.9926    0.8881     1.143
## 
## Concordance= 0.596  (se = 0.032 )
## Likelihood ratio test= 9.33  on 3 df,   p=0.03
## Wald test            = 11.84  on 3 df,   p=0.008
## Score (logrank) test = 10.27  on 3 df,   p=0.02,   Robust = 9.92  p=0.02
## 
##   (Note: the likelihood ratio and score tests assume independence of
##      observations within a cluster, the Wald and robust score tests do not).

6.1 Python Code for repeated events

Python can do the first model mentioned above (treating each individual as a cluster). It cannot incorporate the stratified models.

from lifelines import CoxTimeVaryingFitter
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

bladder = r.bladder

bladder1 = bladder.drop('enum',axis=1)

ctv = CoxTimeVaryingFitter()
ctv.fit(bladder1, id_col="id", event_col="event", start_col="start", stop_col="stop", show_progress=True)
## Iteration 1: norm_delta = 4.39e-01, step_size = 0.9500, log_lik = -458.73935, newton_decrement = 9.76e+00, seconds_since_start = 0.0
## Iteration 2: norm_delta = 5.93e-02, step_size = 0.9500, log_lik = -450.30769, newton_decrement = 3.17e-01, seconds_since_start = 0.0
## Iteration 3: norm_delta = 5.88e-03, step_size = 0.9500, log_lik = -449.98339, newton_decrement = 2.74e-03, seconds_since_start = 0.0
## Iteration 4: norm_delta = 3.39e-04, step_size = 1.0000, log_lik = -449.98065, newton_decrement = 8.10e-06, seconds_since_start = 0.0
## Iteration 5: norm_delta = 8.73e-08, step_size = 1.0000, log_lik = -449.98064, newton_decrement = 5.25e-13, seconds_since_start = 0.0
## Convergence completed after 5 iterations.
## <lifelines.CoxTimeVaryingFitter: fitted with 178 periods, 85 subjects, 112 events>
ctv.print_summary()
## <lifelines.CoxTimeVaryingFitter: fitted with 178 periods, 85 subjects, 112 events>
##          event col = 'event'
## number of subjects = 85
##  number of periods = 178
##   number of events = 112
## partial log-likelihood = -449.98
##   time fit was run = 2024-10-23 14:09:47 UTC
## 
## ---
##            coef exp(coef)  se(coef)  coef lower 95%  coef upper 95% exp(coef) lower 95% exp(coef) upper 95%
## covariate                                                                                                  
## rx        -0.46      0.63      0.20           -0.86           -0.07                0.42                0.93
## number     0.17      1.19      0.05            0.08            0.27                1.09                1.31
## size      -0.04      0.96      0.07           -0.18            0.09                0.84                1.10
## 
##            cmp to     z      p  -log2(p)
## covariate                               
## rx           0.00 -2.33   0.02      5.64
## number       0.00  3.72 <0.005     12.27
## size         0.00 -0.63   0.53      0.92
## ---
## Partial AIC = 905.96
## log-likelihood ratio test = 17.52 on 3 df
## -log2(p) of ll-ratio test = 10.82
ctv.plot()