joineRML

Graeme L. Hickey

2017-03-27

Introduction

The joineRML package implements methods for analyzing data from multiple longitudinal studies in which the responses from each subject consists of time-sequences of repeated measurements and a possibly censored time-to-event outcome. The modelling framework for the repeated measurements is the multivariate linear mixed effects model. The model for the time-to-event outcome is a Cox proportional hazards model with log-Gaussian frailty. Stochastic dependence is captured by allowing the Gaussian random effects of the linear model to be correlated with the frailty term of the Cox proportional hazards model. For full details of the model, please consult the technical vignette by running

vignette("technical", package = "joineRML")

Heart valve data

Data

The simplest way to explain the concepts of the package is through an example. joineRML comes with the data set heart.valve. Details of this data can be found in the help file by running the command

help("heart.valve", package = "joineRML")

This data is in so-called long or unbalanced format:

library("joineRML")
data("heart.valve")
head(heart.valve)
##   num sex      age      time    fuyrs status grad log.grad   lvmi log.lvmi
## 1   1   0 75.06027 0.0109589 4.956164      0   10 2.302585 118.98 4.778955
## 2   1   0 75.06027 3.6794520 4.956164      0   10 2.302585 118.98 4.778955
## 3   1   0 75.06027 4.6958900 4.956164      0   10 2.302585 137.63 4.924569
## 4   2   0 45.79452 6.3643840 9.663014      0   14 2.639057 114.93 4.744323
## 5   2   0 45.79452 7.3041100 9.663014      0    9 2.197225 109.80 4.698661
## 6   2   0 45.79452 8.3013700 9.663014      0   12 2.484907 157.40 5.058790
##   ef  bsa lvh prenyha redo size con.cabg creat dm acei lv emergenc hc
## 1 93 1.77   1       3    0   27        1   103  0    1  1        0  0
## 2 93 1.77   1       3    0   27        1   103  0    1  1        0  0
## 3 93 1.77   1       3    0   27        1   103  0    1  1        0  0
## 4 68 1.92   1       1    1   22        0    76  0    0  2        0  0
## 5 70 1.92   1       1    1   22        0    76  0    0  2        0  0
## 6 56 1.92   1       1    1   22        0    76  0    0  2        0  0
##   sten.reg.mix              hs
## 1            1 Stentless valve
## 2            1 Stentless valve
## 3            1 Stentless valve
## 4            1       Homograft
## 5            1       Homograft
## 6            1       Homograft

The data refer to 256 patients and are stored in the unbalanced format, which is convenient here because measurement times were unique to each subject. The data are stored as a single R object, heart.valve, which is a data frame of dimension 988 by 25. The average number of repeated measurements per subject is therefore 988/256 = 3.86. As with any unbalanced data set, values of time-constant variables are repeated over all rows that refer to the same subject. The dimensionality of the data set can be confirmed by a call to the dim() function, whilst the names of the 25 variables can be listed by a call to the names() function:

dim(heart.valve)
## [1] 988  25
names(heart.valve)
##  [1] "num"          "sex"          "age"          "time"        
##  [5] "fuyrs"        "status"       "grad"         "log.grad"    
##  [9] "lvmi"         "log.lvmi"     "ef"           "bsa"         
## [13] "lvh"          "prenyha"      "redo"         "size"        
## [17] "con.cabg"     "creat"        "dm"           "acei"        
## [21] "lv"           "emergenc"     "hc"           "sten.reg.mix"
## [25] "hs"

We will only analyse a subset of this data, namely records with case-complete data for heart valve gradient (grad) and left ventricular mass index (lvmi):

hvd <- heart.valve[!is.na(heart.valve$grad) & !is.na(heart.valve$lvmi), ]

Strictly speaking, this is not necessary because joineRML can handle the situation of different measurement schedules within subjects That is, a subject does not need to have all multiple longitudinal outcomes recorded at each visit. It is conceivable that some biomarkers will be measured more or less frequently than others. For example, invasive measurements may only be recorded annually, whereas a simple biomarker measurement might be recorded more frequently. joineRML can handle this situation by specifying each longitudinal outcome its own data frame.

Model fitting

The main function in the joineRML package is the mjoint() function. Its main (required) arguments are:

We can fit a bivariate joint model to the log-transformed valve gradient and LVMI indices in the hvd subset using

set.seed(12345)
fit <- mjoint(
  formLongFixed = list("grad" = log.grad ~ time + sex + hs, 
                       "lvmi" = log.lvmi ~ time + sex),
  formLongRandom = list("grad" = ~ 1 | num,
                        "lvmi" = ~ time | num),
  formSurv = Surv(fuyrs, status) ~ age,
  data = list(hvd, hvd),
  inits = list("gamma" = c(0.11, 1.51, 0.80)),
  timeVar = "time")
## Running multivariate LMM EM algorithm to establish initial parameters...
## Finished multivariate LMM EM algorithm...
## EM algorithm has converged!
## Estimating posterior random effects...
## Estimating approximate standard errors...

Details on the model estimation algorithm are provided in the technical details vignette. We note here that this is not necessarily the most appropriate model for the data, and is included only for the purposes of demonstration. There are a number of other useful arguments in the mjoint function; for example, inits for specifying (partial) initial values, control for controlling the optimization algorithm, and verbose for monitoring the convergence output in real-time. A full list of all arguments with explanation are given in the help documentation, accessed by running help("mjoint").

Post-fit analysis

Once we have a fitted mjoint object, we can begin to extract relevant information from it. Most summary statistics are available from the summary function:

summary(fit)
## 
## Call:
## mjoint(formLongFixed = list(grad = log.grad ~ time + sex + hs, 
##     lvmi = log.lvmi ~ time + sex), formLongRandom = list(grad = ~1 | 
##     num, lvmi = ~time | num), formSurv = Surv(fuyrs, status) ~ 
##     age, data = list(hvd, hvd), timeVar = "time", inits = list(gamma = c(0.11, 
##     1.51, 0.8)))
## 
## Data Descriptives:
## 
## Event Process
##     Number of subjects: 221 
##     Number of events: 47 (21.3%)
## 
## Longitudinal Process
##     Number of longitudinal outcomes: K = 2 
##     Number of observations:
##       Outcome 1 (grad): n = 629
##       Outcome 2 (lvmi): n = 629
## 
## Joint Model Summary:
## 
## Longitudinal Process: Multivariate linear mixed-effects model
##      log.grad ~ time + sex + hs, random = ~1 | num
##      log.lvmi ~ time + sex, random = ~time | num
## Event Process: Cox proportional hazards model
##      Surv(fuyrs, status) ~ age
## Model fit statistics:
##    log.Lik      AIC      BIC
##  -991.1086 2018.217 2079.384
## 
## Variance Components:
## 
## Random effects variance covariance matrix
##               (Intercept)_1 (Intercept)_2     time_2
## (Intercept)_1     0.1055400     0.0193350  0.0036377
## (Intercept)_2     0.0193350     0.1190200 -0.0062963
## time_2            0.0036377    -0.0062963  0.0024704
##   Standard Deviations: 0.32487 0.345 0.049703 
## 
## Residual standard errors:
##  sigma2_1  sigma2_2 
## 0.5960814 0.1926246 
## 
## Coefficient Estimates:
## 
## Longitudinal sub-model:
##                       Value Std.Err z-value p-value
## (Intercept)_1        2.5080      NA      NA      NA
## time_1              -0.0043      NA      NA      NA
## sex_1                0.1440      NA      NA      NA
## hsStentless valve_1  0.1883      NA      NA      NA
## (Intercept)_2        5.0895      NA      NA      NA
## time_2              -0.0098      NA      NA      NA
## sex_2               -0.1991      NA      NA      NA
## 
## Time-to-event sub-model:
##          Value Std.Err z-value p-value
## age     0.1088      NA      NA      NA
## gamma_1 1.5098      NA      NA      NA
## gamma_2 0.7998      NA      NA      NA
## 
## Algorithm Summary:
##     EM algorithm computational time: 1.4 mins 
##     Convergence status: converged
##     Convergence criterion: rel 
##     Final Monte Carlo sample size: 9864 
##     Standard errors calculated using method: none

One can also extract the coefficients, fixed effects, and random effects using standard generic functions:

coef(fit)
## $D
##               (Intercept)_1 (Intercept)_2       time_2
## (Intercept)_1   0.105541499   0.019335206  0.003637661
## (Intercept)_2   0.019335206   0.119024648 -0.006296319
## time_2          0.003637661  -0.006296319  0.002470415
## 
## $beta
##       (Intercept)_1              time_1               sex_1 
##         2.507990720        -0.004331581         0.143973057 
## hsStentless valve_1       (Intercept)_2              time_2 
##         0.188349421         5.089548965        -0.009812352 
##               sex_2 
##        -0.199052884 
## 
## $sigma2
##   sigma2_1   sigma2_2 
## 0.35531308 0.03710424 
## 
## $haz
##  [1] 0.001867826 0.001875450 0.002018496 0.002226704 0.002261803
##  [6] 0.002338353 0.002395523 0.002399009 0.002437234 0.002453750
## [11] 0.002467982 0.002676630 0.002731867 0.002790720 0.002811130
## [16] 0.002846033 0.002896472 0.003031751 0.003163636 0.003370386
## [21] 0.003653810 0.007652504 0.004253733 0.004472495 0.004576831
## [26] 0.004769826 0.005182387 0.005310934 0.006232990 0.006768813
## [31] 0.006937431 0.007149492 0.007261701 0.007855373 0.008525877
## [36] 0.008887759 0.009247539 0.010047963 0.011935920 0.012309817
## [41] 0.013645501 0.016502673 0.017026471 0.046465014 0.048612151
## [46] 0.247823020
## 
## $gamma
##       age   gamma_1   gamma_2 
## 0.1088036 1.5098426 0.7997818
fixef(fit, process = "Longitudinal")
##       (Intercept)_1              time_1               sex_1 
##         2.507990720        -0.004331581         0.143973057 
## hsStentless valve_1       (Intercept)_2              time_2 
##         0.188349421         5.089548965        -0.009812352 
##               sex_2 
##        -0.199052884
fixef(fit, process = "Event")
##       age   gamma_1   gamma_2 
## 0.1088036 1.5098426 0.7997818
head(ranef(fit))
##   (Intercept)_1 (Intercept)_2      time_2
## 1   -0.20316979  -0.245247725 0.010554405
## 2   -0.04374375  -0.176936931 0.001851053
## 3   -0.01662837   0.006756923 0.010596340
## 4   -0.42670589  -0.596634955 0.012578630
## 5   -0.05173363   0.077710600 0.021698158
## 6    0.23518088   0.230203068 0.006176545

Although a model fit may indicate convergence, it is generally a good idea to examine the convergence plots. These can be viewed using the plot function for each group of model parameters.

plot(fit, params = "gamma")

plot(fit, params = "beta")

Bootstrap standard errors

Once an mjoint model has converged, and assuming the se.approx argument is TRUE (default), then approximated standard errors are calculated based on the empirical information matrix of the profile likelihood at the maximizer. Theoretically, these standard errors will be underestimated (see the technical vignette). In principle, residual Monte Carlo error will oppose this through an increase in uncertainty.

fit.se <- bootSE(fit, nboot = 25, use.mle = TRUE, control = list(
    nMCmax = 10000, burnin = 20, mcmaxIter = 50, 
    convCrit = "abs", tol0 = 1e-02, tol2 = 1e-02),
  progress = FALSE)
## Warning in bootSE(fit, nboot = 25, use.mle = TRUE, control = list(nMCmax =
## 10000, : Unknown arguments passed to 'control': burnin
## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

## Warning in mjoint(formLongFixed = formLongFixed, formLongRandom =
## formLongRandom, : Unknown arguments passed to 'control': earlyPhase

Bootstrapping is a computationally intensive method, possibly taking many hours to fit. For this reason, the bootSE function makes use of the use.mle argument, which automatically initializes each model fit to the maximizer of the fitted model, fit. Also, one might relax the control parameter constraints on the optimization algorithm for each bootstrap model; however, this will be at the expense of inflated standard errors due to Monte Carlo error.

Note that in the interests of compiling this vignette in a minimum amount of time, we have limited bootSE to only 25 iterations. In general, users will want at least 50-100 iterations. Also, we have increased the convergence tolerance limits to \(10^{-2}\) and used an absolute change criterion. In general, stricter criteria will be required for reliable standard error estimates; the default is \(5 \times 10^{-3}\).

We can call the bootSE object to interrogate it

fit.se
## 
## Bootstrap SE estimates and percentile (95%) confidence intervals
## 
##                        Coefficient Estimate     SE 95% CI Lower
##   Longitudinal       (Intercept)_1   2.5080 0.0515       2.4136
##                             time_1  -0.0043 0.0116      -0.0207
##                              sex_1   0.1440 0.0803       0.0176
##                hsStentless valve_1   0.1883 0.0675       0.0934
##                      (Intercept)_2   5.0895 0.0303       5.0172
##                             time_2  -0.0098 0.0075      -0.0237
##                              sex_2  -0.1991 0.0433      -0.2762
##  Time-to-event                 age   0.1088 0.0239       0.0862
##                            gamma_1   1.5098 1.0289       0.5209
##                            gamma_2   0.7998 0.7827      -0.9452
##    Residual SE            sigma2_1   0.3553 0.0359       0.3008
##                           sigma2_2   0.0371 0.0057       0.0232
##  95% CI Upper
##        2.6004
##        0.0178
##        0.2755
##        0.3169
##        5.1232
##        0.0035
##       -0.1285
##        0.1730
##        4.1893
##        1.7147
##        0.4122
##        0.0436
## 
## Bootstrap computational time: 5.1 mins
## Bootstrap model convergence rate: 92%

or alternatively re-run the summary command, passing the additional argument of bootSE = fit.se

summary(fit, bootSE = fit.se)
## 
## Call:
## mjoint(formLongFixed = list(grad = log.grad ~ time + sex + hs, 
##     lvmi = log.lvmi ~ time + sex), formLongRandom = list(grad = ~1 | 
##     num, lvmi = ~time | num), formSurv = Surv(fuyrs, status) ~ 
##     age, data = list(hvd, hvd), timeVar = "time", inits = list(gamma = c(0.11, 
##     1.51, 0.8)))
## 
## Data Descriptives:
## 
## Event Process
##     Number of subjects: 221 
##     Number of events: 47 (21.3%)
## 
## Longitudinal Process
##     Number of longitudinal outcomes: K = 2 
##     Number of observations:
##       Outcome 1 (grad): n = 629
##       Outcome 2 (lvmi): n = 629
## 
## Joint Model Summary:
## 
## Longitudinal Process: Multivariate linear mixed-effects model
##      log.grad ~ time + sex + hs, random = ~1 | num
##      log.lvmi ~ time + sex, random = ~time | num
## Event Process: Cox proportional hazards model
##      Surv(fuyrs, status) ~ age
## Model fit statistics:
##    log.Lik      AIC      BIC
##  -991.1086 2018.217 2079.384
## 
## Variance Components:
## 
## Random effects variance covariance matrix
##               (Intercept)_1 (Intercept)_2     time_2
## (Intercept)_1     0.1055400     0.0193350  0.0036377
## (Intercept)_2     0.0193350     0.1190200 -0.0062963
## time_2            0.0036377    -0.0062963  0.0024704
##   Standard Deviations: 0.32487 0.345 0.049703 
## 
## Residual standard errors:
##  sigma2_1  sigma2_2 
## 0.5960814 0.1926246 
## 
## Coefficient Estimates:
## 
## Longitudinal sub-model:
##                       Value Std.Err  z-value p-value
## (Intercept)_1        2.5080  0.0515  48.6713 <0.0001
## time_1              -0.0043  0.0116  -0.3719  0.7100
## sex_1                0.1440  0.0803   1.7932  0.0729
## hsStentless valve_1  0.1883  0.0675   2.7886  0.0053
## (Intercept)_2        5.0895  0.0303 168.0013 <0.0001
## time_2              -0.0098  0.0075  -1.3032  0.1925
## sex_2               -0.1991  0.0433  -4.5928 <0.0001
## 
## Time-to-event sub-model:
##          Value Std.Err z-value p-value
## age     0.1088  0.0239  4.5578 <0.0001
## gamma_1 1.5098  1.0289  1.4675  0.1422
## gamma_2 0.7998  0.7827  1.0219  0.3068
## 
## Algorithm Summary:
##     EM algorithm computational time: 1.4 mins 
##     Convergence status: converged
##     Convergence criterion: rel 
##     Final Monte Carlo sample size: 9864 
##     Standard errors calculated using method: boot
##     Number of bootstraps: B = 25 
##     Bootstrap computational time: 5.1 mins

Univariate joint models: joineRML versus joineR

There are a growing number of software options for fitting joint models of a single longitudinal outcome and a single time-to-event outcome; what we call here univariate joint models. joineR (version 1.0-3) is one package available in R for fitting such models, however joineRML can fit these models too, since the univariate model is simply a special case of the multivariate model. It is useful to contrast these two packages. There are theoretical and practical implementation differences between the packages beyond just univariate versus multivariate capability:

To fit a univariate model in joineR we run the following code for the hvd data

library(joineR, quietly = TRUE)
hvd.surv <- UniqueVariables(hvd, var.col = c("fuyrs", "status"), id.col = "num")
hvd.cov <- UniqueVariables(hvd, "age", id.col = "num")
hvd.long <- hvd[, c("num", "time", "log.lvmi")]

hvd.jd <- jointdata(longitudinal = hvd.long, 
                    baseline = hvd.cov, 
                    survival = hvd.surv, 
                    id.col = "num", 
                    time.col = "time")

system.time(fit.joiner <- joint(data = hvd.jd,
                                long.formula = log.lvmi ~ time + age, 
                                surv.formula = Surv(fuyrs, status) ~ age, 
                                model = "intslope"))
##    user  system elapsed 
##   1.201   0.065   1.265
summary(fit.joiner)
## Random effects joint model
##  Data: hvd.jd 
##  Log-likelihood: -373.2656 
## 
## Longitudinal sub-model fixed effects: log.lvmi ~ time + age                         
## (Intercept)  4.9979861781
## time        -0.0096970943
## age          0.0004407454
## 
## Survival sub-model fixed effects: Surv(fuyrs, status) ~ age             
## age 0.1058821
## 
## Latent association:                
## gamma_0 1.072404
## 
## Variance components:
##         U_0         U_1    Residual 
## 0.128470868 0.002561035 0.037055009 
## 
## Convergence at iteration: 18 
## 
## Number of observations: 629 
## Number of groups: 221
system.time(fit.joiner.boot <- jointSE(fit.joiner, n.boot = 100))
##    user  system elapsed 
## 110.155   0.889 111.503
fit.joiner.boot
##      Component   Parameter Estimate     SE 95%Lower 95%Upper
## 1 Longitudinal (Intercept)    4.998 0.1423   4.7488   5.2666
## 2                     time  -0.0097 0.0074  -0.0263   0.0032
## 3                      age    4e-04  0.002  -0.0038   0.0036
## 4     Survival         age   0.1059 0.0188   0.0672   0.1385
## 5  Association     gamma_0   1.0724 0.5083  -0.1413   1.9029
## 6     Variance         U_0   0.1285 0.0183    0.092   0.1611
## 7                      U_1   0.0026  9e-04   0.0012   0.0049
## 8                 Residual   0.0371 0.0051   0.0261   0.0465

To fit a univariate model in joineRML we run the following code for the hvd data

set.seed(123)
fit.joinerml <- mjoint(formLongFixed = log.lvmi ~ time + age,
                       formLongRandom = ~ time | num,
                       formSurv = Surv(fuyrs, status) ~ age,
                       data = hvd,
                       timeVar = "time")
## EM algorithm has converged!
## Calculating post model fit statistics...
summary(fit.joinerml)
## 
## Call:
## mjoint(formLongFixed = log.lvmi ~ time + age, formLongRandom = ~time | 
##     num, formSurv = Surv(fuyrs, status) ~ age, data = hvd, timeVar = "time")
## 
## Data Descriptives:
## 
## Event Process
##     Number of subjects: 221 
##     Number of events: 47 (21.3%)
## 
## Longitudinal Process
##     Number of longitudinal outcomes: K = 1 
##     Number of observations:
##       Outcome 1: n = 629
## 
## Joint Model Summary:
## 
## Longitudinal Process: Univariate linear mixed-effects model
##      log.lvmi ~ time + age, random = ~time | num
## Event Process: Cox proportional hazards model
##      Surv(fuyrs, status) ~ age
## Model fit statistics:
##    log.Lik      AIC      BIC
##  -373.2707 764.5414 795.1249
## 
## Variance Components:
## 
## Random effects variance covariance matrix
##               (Intercept)_1     time_1
## (Intercept)_1     0.1277800 -0.0065414
## time_1           -0.0065414  0.0024152
##   Standard Deviations: 0.35746 0.049145 
## 
## Residual standard errors:
## sigma2_1 
## 0.193006 
## 
## Coefficient Estimates:
## 
## Longitudinal sub-model:
##                 Value Std.Err z-value p-value
## (Intercept)_1  4.9983  0.1401 35.6856 <0.0001
## time_1        -0.0093  0.0067 -1.4049  0.1600
## age_1          0.0004  0.0021  0.2069  0.8361
## 
## Time-to-event sub-model:
##          Value Std.Err z-value p-value
## age     0.1060  0.0149  7.0969 <0.0001
## gamma_1 1.0846  0.5413  2.0036  0.0451
## 
## Algorithm Summary:
##     EM algorithm computational time: 10.3 secs 
##     Convergence status: converged
##     Convergence criterion: sas 
##     Final Monte Carlo sample size: 418 
##     Standard errors calculated using method: approx
fit.joinerml.boot <- bootSE(fit.joinerml, nboot = 25, use.mle = TRUE, control = 
                            list(burnin = 25, convCrit = "sas",
                                 tol0 = 1e-02, tol2 = 1e-02, mcmaxIter = 60),
                            progress = FALSE)

fit.joinerml.boot
## 
## Bootstrap SE estimates and percentile (95%) confidence intervals
## 
##                  Coefficient Estimate     SE 95% CI Lower 95% CI Upper
##   Longitudinal (Intercept)_1   4.9983 0.1384       4.7734       5.2762
##                       time_1  -0.0093 0.0078      -0.0276       0.0004
##                        age_1   0.0004 0.0019      -0.0035       0.0032
##  Time-to-event           age   0.1060 0.0177       0.0830       0.1488
##                      gamma_1   1.0846 0.5954      -0.2892       1.7233
##    Residual SE      sigma2_1   0.0373 0.0050       0.0288       0.0450
## 
## Bootstrap computational time: 1.4 mins
## Bootstrap model convergence rate: 100%

In addition to just comparing model parameter estimates, we can also extract the predicted (or posterior) random effects from each model and plot them.

id <- as.numeric(row.names(fit.joiner$coefficients$random))
id.ord <- order(id) # joineR rearranges patient ordering during EM fit
par(mfrow = c(1, 2))
plot(fit.joiner$coefficients$random[id.ord, 1], ranef(fit.joinerml)[, 1],
     main = "Predicted random intercepts",
     xlab = "joineR", ylab = "joineRML")
grid()
abline(a = 0, b = 1, col = 2, lty = "dashed")
plot(fit.joiner$coefficients$random[id.ord, 2], ranef(fit.joinerml)[, 2],
     main = "Predicted random slopes",
     xlab = "joineR", ylab = "joineRML")
grid()
abline(a = 0, b = 1, col = 2, lty = "dashed")