Closed swihart closed 5 years ago
This article seems to imply that no software as of June 2018 could do it:
My PhD student Greg Papageorgiou has implemented a multi state models extension in JMbayes. This should also fit competing risks settings.
@Greg, could you provide an example.
From: Bruce Swihart notifications@github.com<mailto:notifications@github.com> Date: Tuesday, 23 Apr 2019, 17:30 To: drizopoulos/JMbayes JMbayes@noreply.github.com<mailto:JMbayes@noreply.github.com> Cc: Subscribed subscribed@noreply.github.com<mailto:subscribed@noreply.github.com> Subject: Re: [drizopoulos/JMbayes] How feasible to add CompRisk option to (#39)
This article seems to imply that no software as of June 2018 could do it:
"Competing risks [28, 29] and recurrent events [38] have been incorporated into joint modelling R packages already, but are limited to the case of a solitary longitudinal outcome. "https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.ncbi.nlm.nih.gov%2Fpmc%2Farticles%2FPMC6047371%2F%23Sec14title&data=02%7C01%7Cd.rizopoulos%40erasmusmc.nl%7C737506594b974a6d2eb008d6c7f84c6e%7C526638ba6af34b0fa532a1a511f4ac80%7C0%7C0%7C636916266579255566&sdata=2M20K%2Fv%2BJ7asxtLeVf9yhPozQzZ7l4XWyL8UwGTMS24%3D&reserved=0
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHubhttps://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fdrizopoulos%2FJMbayes%2Fissues%2F39%23issuecomment-485828092&data=02%7C01%7Cd.rizopoulos%40erasmusmc.nl%7C737506594b974a6d2eb008d6c7f84c6e%7C526638ba6af34b0fa532a1a511f4ac80%7C0%7C0%7C636916266579265575&sdata=spMaLDuDwarqfahvMm7GpvgTcq7bxxXZask1jLMZjOY%3D&reserved=0, or mute the threadhttps://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnotifications%2Funsubscribe-auth%2FADE7TT47FPRWAE7DFANXWBLPR4MR7ANCNFSM4HHYOMGQ&data=02%7C01%7Cd.rizopoulos%40erasmusmc.nl%7C737506594b974a6d2eb008d6c7f84c6e%7C526638ba6af34b0fa532a1a511f4ac80%7C0%7C0%7C636916266579275583&sdata=%2BX4u%2FyhEJQqw0kKA23V7lJRfmMZf%2BoSnNxl6kazTl7A%3D&reserved=0.
Here's my attempt at reprex. Any help would be appreciated. (UPDATE: scroll down to @gpapageorgiou response and my updated reprex below it)
library(JM)
library(JMbayes)
## slides: http://www.drizopoulos.com/courses/EMC/ESP72.pdf
## slide 163
pbc2.idCR <- crLong(pbc2.id, statusVar = "status",
censLevel = "alive", nameStrata = "CR")
pbc2.idCR[pbc2.idCR$id %in% c(1,2,5),
c("id", "years", "status", "CR", "status2")]
## slide 164
coxFit.CR <- coxph(Surv(years, status2) ~ drug * strata(CR),
data = pbc2.idCR, x = TRUE)
## slide 164 & 165
lmeFit.CR <- lme(log(serBilir) ~ drug * year, data = pbc2,
random = ~ year | id)
jointFit.CR <- jointModel(lmeFit.CR, coxFit.CR, timeVar = "year",
method = "spline-PH-aGH", CompRisk = TRUE,
interFact = list(value = ~ CR, data = pbc2.idCR))
summary(jointFit.CR)
## follow section 5 of
## https://cran.r-project.org/web/packages/mstate/vignettes/Tutorial.pdf
pbc2.id[pbc2.id$id %in% c(1,2,5),
c("id", "years", "status", "status2")]
library(mstate)
?trans.comprisk
tmat <- trans.comprisk(2, names = c("alive", "transplanted", "dead"))
tmat
pbc2.id$statTX <- as.numeric(pbc2.id$status=="transplanted")
pbc2.id$statDD <- as.numeric(pbc2.id$status=="dead")
?msprep
pbc2.idMS <- msprep(time = c(NA, "years", "years"),
status = c(NA, "statTX", "statDD"),
data = pbc2.id,
keep = "drug",
trans = tmat)
pbc2.idMS[pbc2.idMS$id %in% c(1,2,5),]
coxFit.MS <- coxph(Surv(time, status) ~ drug * strata(trans),
data = pbc2.idMS, model=TRUE)
## reproduce jointFit.CR with mvglmer/mvJointModelBayes (?)
mvglmerFit.MS <- mvglmer(list(log(serBilir) ~ drug * year + (year|id)),
data = pbc2,
families=list(gaussian)
)
jointFit.MS <-
mvJointModelBayes(mvglmerFit.MS,
coxFit.MS,
timeVar="year",
multiState=TRUE,
data_MultiState=pbc2.idMS,
idVar_MultiState = "id"
)
Where the last call (mvJointModelBayes()
) gives the error about different data sizes:
Error in `[[<-.data.frame`(`*tmp*`, timeVar, value = c(`1` = 1.09517029898149, :
replacement has 624 rows, data has 312
But I think I'm on the right track because the following models are similar:
## similar:
coxFit.CR
coxFit.MS
## similar:
summary(lmeFit.CR)
summary(mvglmerFit.MS)
Output:
> ## similar:
> coxFit.CR
Call:
coxph(formula = Surv(years, status2) ~ drug * strata(CR), data = pbc2.idCR,
x = TRUE)
coef exp(coef) se(coef) z p
drugD-penicil -0.3857 0.6800 0.3771 -1.023 0.306
drugD-penicil:strata(CR)dead 0.3840 1.4682 0.4133 0.929 0.353
Likelihood ratio test=1.06 on 2 df, p=0.5877
n= 624, number of events= 169
> coxFit.MS
Call:
coxph(formula = Surv(time, status) ~ drug * strata(trans), data = pbc2.idMS,
model = TRUE, x = TRUE)
coef exp(coef) se(coef) z p
drugD-penicil -0.3857 0.6800 0.3771 -1.023 0.306
drugD-penicil:strata(trans)trans=2 0.3840 1.4682 0.4133 0.929 0.353
Likelihood ratio test=1.06 on 2 df, p=0.5877
n= 624, number of events= 169
and
>## similar:
> summary(lmeFit.CR)
Linear mixed-effects model fit by REML
Data: pbc2
AIC BIC logLik
3085.474 3130.042 -1534.737
Random effects:
Formula: ~year | id
Structure: General positive-definite, Log-Cholesky parametrization
StdDev Corr
(Intercept) 0.9985870 (Intr)
year 0.1722100 0.417
Residual 0.3489329
Fixed effects: log(serBilir) ~ drug * year
Value Std.Error DF t-value p-value
(Intercept) 0.5630313 0.08255260 1631 6.820274 0.0000
drugD-penicil -0.1332474 0.11610813 310 -1.147614 0.2520
year 0.1797540 0.01782101 1631 10.086631 0.0000
drugD-penicil:year -0.0044099 0.02490439 1631 -0.177075 0.8595
Correlation:
(Intr) drgD-p year
drugD-penicil -0.711
year 0.248 -0.176
drugD-penicil:year -0.177 0.251 -0.716
Standardized Within-Group Residuals:
Min Q1 Med Q3 Max
-4.3241109 -0.4987081 -0.0166671 0.4528833 5.2878568
Number of Observations: 1945
Number of Groups: 312
> summary(mvglmerFit.MS)
Call:
mvglmer(formulas = list(log(serBilir) ~ drug * year + (year |
id)), data = pbc2, families = list(gaussian))
Data Descriptives:
Number of Groups: 312
Number of Observations:
log(serBilir) 1945
DIC pD
2363.412 939.6878
Random-effects covariance matrix:
StdDev Corr
(Intercept)1 1.0061 (Int)1
year1 0.1748 0.4013
Outcome: log(serBilir)
PostMean StDev StErr 2.5% 97.5% P Rhat
(Intercept) 0.5660 0.0838 0.0026 0.4076 0.7357 0.000 0.9991
drugD-penicil -0.1320 0.1157 0.0037 -0.3627 0.0880 0.262 1.0071
year 0.1796 0.0190 0.0006 0.1431 0.2187 0.000 0.9994
drugD-penicil:year -0.0039 0.0258 0.0008 -0.0526 0.0492 0.882 1.0003
sigma 0.3494 0.0068 0.0002 0.3366 0.3633 0.000 1.0006
MCMC summary:
engine: JAGS
iterations: 28000
adapt: 3000
burn-in: 3000
thinning: 50
time: 1.3 min
Notes as I try to debug:
Before Line 260 of mvJointModelBayes
, length(unique(dataL[[idVar]]))
is 312, length(dataS[[idVar]])
is 0, and idT
is not defined yet.
After the line 261 idT <- dataS[[idVar]] <- unique(dataL[[idVar]])
, length(unique(dataL[[idVar]]))
is 312, length(dataS[[idVar]])
is 624, and length(idT)
is 312.
And the dataS looks like the id variable is incorrectly assigned, should be 1,1,2,2, ... 312,312.
> head(dataS)
Surv(time, status) drug strata(trans) id
1 1.095170+ D-penicil trans=1 1
2 1.095170 D-penicil trans=2 2
3 14.152338+ D-penicil trans=1 3
4 14.152338+ D-penicil trans=2 4
5 2.770781+ D-penicil trans=1 5
6 2.770781 D-penicil trans=2 6
> tail(dataS)
Surv(time, status) drug strata(trans) id
619 4.402585+ D-penicil trans=1 307
620 4.402585+ D-penicil trans=2 308
621 4.128792+ D-penicil trans=1 309
622 4.128792+ D-penicil trans=2 310
623 3.989158+ placebo trans=1 311
624 3.989158+ placebo trans=2 312
Dear @swihart,
Below the code to reproduce the Competing Risks example of package JM using JMbayes::mvJointModelBayes()
library(JMbayes)
library(mstate)
library(splines)
library(survival)
## Prepare Dataset
pbc2.id$death <- ifelse(pbc2.id$status %in% "dead", 1, 0)
pbc2.id$transplant <- ifelse(pbc2.id$status %in% "transplanted", 1, 0)
## Create transition matrix
tmat <- matrix(NA, 3, 3)
dimnames(tmat) <- list(from = c("alive", "transplanted", "dead"),
to = c("alive", "transplanted", "dead"))
tmat[1, 2:3] <- 1:2
## Create Multi-state version of the dataset
covs <- c("drug", "sex")
pbc2.mstate <- msprep(time = c(NA, "years", "years"),
status = c(NA, "transplant", "death"),
data = pbc2.id,
trans = tmat,
keep = covs,
id = "id")
## Expand covariates per transition
pbc2.mstate <- expand.covs(pbc2.mstate, covs, append = TRUE, longnames = FALSE)
## Fit Competing risks sub-model. Here, drug.1, drug.2, sex.1, sex.2 are
## the expanded covariates from
## the previous step, with drug.1 for example representing
## the effect of drug for transition 1, drug.2
## the effect of drug for transition 2 etc.
coxCRfit <- coxph(Surv(Tstart, Tstop, status) ~ drug.1 + drug.2 + sex.1 + sex.2 +
strata(trans) + cluster(id),
data = pbc2.mstate, x = TRUE, model = TRUE)
## fit mixed-effects sub-model
mixfit <- mvglmer(list(log(serBilir) ~ drug * ns(year, 3) + (ns(year, 3) | id)),
data = pbc2,
families = list(gaussian))
## Specify interactions in order to allow different effect of the outcome for each risk
interacts <- list("log(serBilir)" = ~ strata(trans) - 1)
## fit multistate model
JMfit <- mvJointModelBayes(mixfit, coxCRfit, timeVar = "year",
Interactions = interacts,
multiState = TRUE,
data_MultiState = pbc2.mstate,
idVar_MultiState = "id",
control = list(equal.strata.knots = TRUE,
equal.strata.bound.knots = TRUE))
summary(JMfit)
Thanks @gpapageorgiou and @drizopoulos ! I'm leaving an updated version of what I attempted above FWIW -- comments welcomed but not necessary, of course! Thanks for your help and promptitude!
library(JM)
library(JMbayes)
##############################
## Do things the JM, CR way ##
## Limited to 1 longitudinal##
## process ##
##############################
## slides: http://www.drizopoulos.com/courses/EMC/ESP72.pdf
## slide 163
pbc2.idCR <- crLong(pbc2.id, statusVar = "status",
censLevel = "alive", nameStrata = "CR")
pbc2.idCR[pbc2.idCR$id %in% c(1,2,5),
c("id", "years", "status", "CR", "status2")]
## slide 164
coxFit.CR <- coxph(Surv(years, status2) ~ drug * strata(CR),
data = pbc2.idCR, x = TRUE)
## equivalently:
pbc2.idCR$drug.1 <- (pbc2.idCR$drug=="D-penicil" & pbc2.idCR$CR=="transplanted")+0L
pbc2.idCR$drug.2 <- (pbc2.idCR$drug=="D-penicil" & pbc2.idCR$CR=="dead" )+0L
coxFit.CR2<- coxph(Surv(years, status2) ~ drug.1 + drug.2 + strata(CR),
data = pbc2.idCR, x = TRUE)
## slide 164 & 165
lmeFit.CR <- lme(log(serBilir) ~ drug * year, data = pbc2,
random = ~ year | id)
jointFit.CR <- jointModel(lmeFit.CR, coxFit.CR, timeVar = "year",
method = "spline-PH-aGH", CompRisk = TRUE,
interFact = list(value = ~ CR, data = pbc2.idCR))
## same as jointFit.CR -- different parameterization (use coxFit.CR2 and change interFact).
jointFit.CR2<- jointModel(lmeFit.CR, coxFit.CR2, timeVar = "year",
method = "spline-PH-aGH", CompRisk = TRUE,
interFact = list(value = ~ strata(CR) - 1, data = pbc2.idCR))
###################################
## Do things the JMbayes, MS way ##
## Allows competing risks and >1 ##
## longitudinal process ##
###################################
library(mstate)
## follow section 5 of
## https://cran.r-project.org/web/packages/mstate/vignettes/Tutorial.pdf
pbc2.id[pbc2.id$id %in% c(1,2,5),
c("id", "years", "status", "status2")]
## prepare tmat
tmat <- trans.comprisk(2, names = c("alive", "transplanted", "dead"))
tmat
## use msprep()
pbc2.id$statTX <- as.numeric(pbc2.id$status=="transplanted")
pbc2.id$statDD <- as.numeric(pbc2.id$status=="dead")
?msprep
pbc2.idMS <- msprep(time = c(NA, "years", "years"),
status = c(NA, "statTX", "statDD"),
data = pbc2.id,
keep = "drug",
trans = tmat)
## Crucial step! I omitted this in my previous comment.
## Expand covariates per transition. See before/after expansion.
pbc2.idMS[pbc2.idMS$id %in% c(1,2,5),]
pbc2.idMS <- expand.covs(pbc2.idMS, "drug", append = TRUE, longnames = FALSE)
pbc2.idMS[pbc2.idMS$id %in% c(1,2,5),]
## explicitly put in the expanded covariates and add strata() and add cluster()
coxFit.MS <- coxph(Surv(Tstart, Tstop, status) ~ drug.1 + drug.2 + strata(trans) + cluster(id),
data = pbc2.idMS, model=TRUE, x=TRUE)
## reproduce jointFit.CR with mvglmer/mvJointModelBayes (?)
mvglmerFit.MS <- mvglmer(list(log(serBilir) ~ drug * year + (year|id)),
data = pbc2,
families=list(gaussian)
)
## Crucial step:
## Specify interactions in order to allow different effect of the outcome for each risk
interacts <- list("log(serBilir)" = ~ strata(trans) - 1)
jointFit.MS <-
mvJointModelBayes(mvglmerFit.MS,
coxFit.MS,
timeVar="year",
Interactions=interacts,
multiState=TRUE,
data_MultiState=pbc2.idMS,
idVar_MultiState = "id",
control = list(equal.strata.knots=TRUE,
equal.strata.bound.knots=TRUE)
)
## similar:
summary(jointFit.CR2)
summary(jointFit.MS)
## similar:
coxFit.CR
coxFit.MS
## similar:
summary(lmeFit.CR)
summary(mvglmerFit.MS)
Thanks for this tutorial @gpapageorgiou & @swihart. I was wondering if it was possible to perform dynamic predictions from a competing risk mvJMbayes
object? I've attempted to do so with the following code:
ND <- pbc2[pbc2$id == 2, ] # the data of Subject 2
ND_merge <- merge(ND, coxCRfit$model, by.x = "id", by.y="(cluster)")
names(ND_merge)[names(ND_merge) == 'strata(trans)'] <- 'trans'
str(ND_merge)
'data.frame': 8 obs. of 26 variables:
$ id : num 2 2 2 2 2 2 2 2
$ years : num 14.2 14.2 14.2 14.2 14.2 ...
$ status : Factor w/ 3 levels "alive","transplanted",..: 1 1 1 1 1 1 1 1
$ drug : Factor w/ 2 levels "placebo","D-penicil": 2 2 2 2 2 2 2 2
$ age : num 56.4 56.4 56.4 56.4 56.4 ...
$ sex : Factor w/ 2 levels "male","female": 2 2 2 2 2 2 2 2
$ year : num 0 0 4.9 4.9 7.89 ...
$ ascites : Factor w/ 2 levels "No","Yes": 1 1 2 2 2 2 2 2
$ hepatomegaly : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 2 2 2
$ spiders : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 2 2 2
$ edema : Factor w/ 3 levels "No edema","edema no diuretics",..: 1 1 2 2 3 3 3 3
$ serBilir : num 1.1 1.1 2.6 2.6 3.6 3.6 4.6 4.6
$ serChol : int 302 302 230 230 244 244 237 237
$ albumin : num 4.14 4.14 3.32 3.32 2.8 2.8 2.67 2.67
$ alkaline : int 7395 7395 1110 1110 779 779 669 669
$ SGOT : num 114 114 132 132 119 ...
$ platelets : int 221 221 135 135 113 113 100 100
$ prothrombin : num 10.6 10.6 11.3 11.3 11.5 11.5 11.5 11.5
$ histologic : int 3 3 3 3 3 3 3 3
$ status2 : num 0 0 0 0 0 0 0 0
$ Surv(Tstart, Tstop, status): 'Surv' num [1:8, 1:3] (0,14.2+] (0,14.2+] (0,14.2+] (0,14.2+] ...
..- attr(*, "dimnames")=List of 2
.. ..$ : NULL
.. ..$ : chr "start" "stop" "status"
..- attr(*, "type")= chr "counting"
$ drug.1 : num 1 0 1 0 1 0 1 0
$ drug.2 : num 0 1 0 1 0 1 0 1
$ sex.1 : num 1 0 1 0 1 0 1 0
$ sex.2 : num 0 1 0 1 0 1 0 1
$ trans : Factor w/ 2 levels "trans=1","trans=2": 1 2 1 2 1 2 1 2
survfitJM(CR_JMfit, ND_merge)
Error in `contrasts<-`(`*tmp*`, value = contr.funs[1 + isOF[nn]]) :
contrasts can be applied only to factors with 2 or more levels
The traceback shows below:
traceback()
8: stop("contrasts can be applied only to factors with 2 or more levels")
7: `contrasts<-`(`*tmp*`, value = contr.funs[1 + isOF[nn]])
6: model.matrix.default(term, data = newdata.GK.postRE.i)
5: model.matrix(term, data = newdata.GK.postRE.i)
4: FUN(X[[i]], ...)
3: lapply(TermsU, function(term) {
model.matrix(term, data = newdata.GK.postRE.i)
})
2: survfitJM.mvJMbayes(CR_JMFit, ND_merge)
1: survfitJM(CR_JMFit, ND_merge)
The error message is not too obvious to me and I don't see an apparent reason why it shouldn't work after combining the longitudinal and msprep
data from the cox$model
matrix. I don't see why the one-factor should be an issue when subsetting a singular patient. Any insight would be greatly appreciated.
Harry
@harryparr Thank you for your question.
Currently survfitJM()
does not support mvJMbayes
objects that were fitted using the argument multistate = TRUE
. The actual error message is probably misleading in this case. Both the R and C++ parts of the source code would need to be adjusted for this to work.
Best, Greg
Is it possible for a competing risks model using
JMbayes::mvJointModelBayes()
? Similar to Slide 165 of your presentation.If not, how feasible would it be for
JMbayes::mvJointModelBayes()
to acquireCompRisk=TRUE
option for fitting competing risks likeJM::jointModel()
? I'm willing to try a PR if it is deemed feasible. Any tips on where in the code to start?Thanks, as always, for a great body of work and documentation.