Univariate models

Function for the calibration curve for the univariate joint model:

calJM <- function(object, newdata, Tstart, Thoriz, idVar = "id", 
                  simulate = FALSE, M = 100, pl = TRUE, 
                  include.cens = FALSE, ...) {
  if (!inherits(object, "JMbayes")) 
    stop("Use only with 'JMbayes' objects.\n")
  if (!is.data.frame(newdata) || nrow(newdata) == 0) 
    stop("'newdata' must be a data.frame with more than one rows.\n")
  if (is.null(newdata[[idVar]])) 
    stop("'idVar' not in 'newdata.\n'")
 
  id <- newdata[[idVar]]
  id <- match(id, unique(id))
  TermsT <- object$Terms$termsT
  SurvT <- model.response(model.frame(TermsT, newdata))
  is_counting <- attr(SurvT, "type") == "counting"
  Time <- if (is_counting) {
    ave(SurvT[, 2], id, FUN = function(x) tail(x, 1))
  } else {
    SurvT[, 1]
  }
  timeVar <- object$timeVar
  newdata2 <- newdata[Time > Tstart, ]
  SurvT <- model.response(model.frame(TermsT, newdata2))
  if (is_counting) {
    id2 <- newdata2[[idVar]]
    f <- factor(id2, levels = unique(id2))
    Time <- ave(SurvT[, 2], f, FUN = function(x) tail(x, 1))
    delta <- ave(SurvT[, 3], f, FUN = function(x) tail(x, 1))
  } else {
    Time <- SurvT[, 1]
    delta <- SurvT[, 2]
  }
  timesInd <- newdata2[[timeVar]] <= Tstart
  aliveThoriz <- newdata2[Time > Thoriz & timesInd, ]
  deadThoriz <- newdata2[Time <= Thoriz & delta == 1 & timesInd, ]
  indCens <- Time < Thoriz & delta == 0 & timesInd
  censThoriz <- newdata2[indCens, ]
  nr <- length(unique(newdata2[[idVar]]))
  idalive <- unique(aliveThoriz[[idVar]])
  iddead <- unique(deadThoriz[[idVar]])
  idcens <- unique(censThoriz[[idVar]])

  Surv.aliveThoriz <- if (is_counting) {
    survfitJM(object, newdata = aliveThoriz, idVar = idVar, 
              simulate = simulate, M = M, survTimes = Thoriz, 
              last.time = rep(Tstart, length(idalive)), LeftTrunc_var = all.vars(TermsT)[1L])
  } else {
    survfitJM(object, newdata = aliveThoriz, idVar = idVar, 
              simulate = simulate, M = M, survTimes = Thoriz, 
              last.time = rep(Tstart, length(idalive)))
  }
  Surv.deadThoriz <- if (is_counting) {
    survfitJM(object, newdata = deadThoriz, idVar = idVar, 
              simulate = simulate, survTimes = Thoriz, 
              last.time = rep(Tstart, length(iddead)), LeftTrunc_var = all.vars(TermsT)[1L])
  } else {
    survfitJM(object, newdata = deadThoriz, idVar = idVar, 
              simulate = simulate, survTimes = Thoriz, last.time = rep(Tstart, length(iddead)))
  }
  Surv.aliveThoriz <- sapply(Surv.aliveThoriz$summaries, "[", 2)
  Surv.deadThoriz <- sapply(Surv.deadThoriz$summaries, "[", 2)
  if (nrow(censThoriz)) {
    Surv.censThoriz <- if (is_counting) {
      survfitJM(object, newdata = censThoriz, idVar = idVar, 
                simulate = simulate, M = M, survTimes = Thoriz, 
                last.time = rep(Tstart, length(idcens)), LeftTrunc_var = all.vars(TermsT)[1L])
    } else {
      survfitJM(object, newdata = censThoriz, idVar = idVar, 
                simulate = simulate, M = M, survTimes = Thoriz, 
                last.time = rep(Tstart, length(idcens)))
    }
    tt <- Time[indCens]
    weights <- if (is_counting) {
      survfitJM(object, newdata = censThoriz, idVar = idVar, 
                simulate = simulate, M = M, survTimes = Thoriz, 
                last.time = tt[!duplicated(censThoriz[[idVar]])], 
                LeftTrunc_var = all.vars(TermsT)[1L])
    }
    else {
      survfitJM(object, newdata = censThoriz, idVar = idVar, 
                simulate = simulate, M = M, survTimes = Thoriz, 
                last.time = tt[!duplicated(censThoriz[[idVar]])])
    }
    Surv.censThoriz <- sapply(Surv.censThoriz$summaries, 
                              "[", 2)
    weights <- sapply(weights$summaries, "[", 2)
  } else {
    Surv.censThoriz <- weights <- NA
  }
  
  # until here I used the prederrJM function from the JMbayes package

  newdata.id <- newdata[tapply(row.names(newdata), newdata$id, tail, 1), ]
  
  statusCens <- rbinom(n = length(weights), size = 1, prob = weights)
  matCens <- data.frame(years = newdata.id$years[newdata.id$id %in% names(Surv.censThoriz)], 
                         pred_prob = 1 - Surv.censThoriz, status = 1 - statusCens)
  
  # New part for calibration plot
  matDead <- data.frame(years = newdata.id$years[newdata.id$id %in% names(Surv.deadThoriz)], 
                         pred_prob = 1 - Surv.deadThoriz, status = 1)
  matAlive <- data.frame(years = newdata.id$years[newdata.id$id %in% names(Surv.aliveThoriz)], 
                         pred_prob = 1 - Surv.aliveThoriz, status = 0)
  if (include.cens == TRUE) {
    mat <- rbind(matDead, matAlive, matCens)
  } else {
    mat <- rbind(matDead, matAlive)
  }
 
  # Cox model calibration curve
  #  The complementary log-log transformation for the predicted probabilities 
  #  rather than the probabilities themselves.
  mat$cox.cll <- log(-log(1 - mat$pred_prob))
  # Use a conventional Cox proportional hazards model with restricted cubic 
  #  splines to model the relationship between  
  #  log(-log(1-pred)) and the log-hazard of the outcome
  calibrate.cox <- coxph(Surv(years, status) ~ rcs(cox.cll, 3), x = T,
                         data = mat) 
  # Based on the fitted model, an estimated probability of the occurrence of the
  #  outcome prior to time t_0 can be estimated for each value of pred
  predict.grid.cox <- seq(0.01,
                          0.99, length  = 100)
  predict.grid.cox.cll <- log(-log(1 - predict.grid.cox))
  
  predict.grid.cox.df <- data.frame(predict.grid.cox)
  predict.grid.cox.cll.df <- data.frame(predict.grid.cox.cll)
  
  names(predict.grid.cox.df) <- "cox"
  names(predict.grid.cox.cll.df) <- "cox.cll"

  predict.calibrate.cox <- 1 - predictSurvProb(calibrate.cox,
                                               newdata = predict.grid.cox.cll.df,
                                               times = Thoriz)
  
  # Save results
  out <- list(x = predict.grid.cox, y = predict.calibrate.cox)
  
  if (pl == TRUE) {
    plot(predict.grid.cox, predict.calibrate.cox, type="l", lty=1, col="red",
         xlim=c(0,1),ylim=c(0,1),
         xlab = "Predicted probability",
         ylab = "Observed probability")
    abline(0, 1, lwd = 6, col = gray(0.85))
  }
  out
}

Multivariate models

Function for the calibration curve for the multivariate joint model:

calJM.mv <- function(object, newdata, Tstart, Thoriz, idVar = "id", 
                  simulate = FALSE, M = 100, pl = TRUE, 
                  include.cens = FALSE, ...) {
  if (!inherits(object, "mvJMbayes"))
    stop("Use only with 'mvJMbayes' objects.\n")
  if (!is.data.frame(newdata) || nrow(newdata) == 0)
    stop("'newdata' must be a data.frame with more than one rows.\n")
  if (is.null(newdata[[idVar]]))
    stop("'idVar' not in 'newdata.\n'")
  id <- newdata[[idVar]]
  id <- match(id, unique(id))
  TermsT <- object$model_info$coxph_components$Terms
  environment(TermsT) <- parent.frame()#.GlobalEnv
  SurvT <- model.response(model.frame(TermsT, newdata)) 
  is_counting <- attr(SurvT, "type") == "counting"
  is_interval <- attr(SurvT, "type") == "interval"
  Time <- if (is_counting) {
    ave(SurvT[, 2], id, FUN = function (x) tail(x, 1))
  } else if (is_interval) {
    Time1 <- SurvT[, "time1"]
    Time2 <- SurvT[, "time2"]
    Time <- Time1
    Time[Time2 != 1] <- Time2[Time2 != 1]
    Time
  } else {
    SurvT[, 1]
  }
  timeVar <- object$model_info$timeVar
  newdata2 <- newdata[Time > Tstart, ]
  id2 <- newdata2[[idVar]]
  SurvT <- model.response(model.frame(TermsT, newdata2))
  if (is_counting) {
    f <- factor(id2, levels = unique(id2))
    Time <- ave(SurvT[, 2], f, FUN = function (x) tail(x, 1))
    event <- ave(SurvT[, 3], f, FUN = function (x) tail(x, 1))
  } else if (is_interval) {
    Time1 <- SurvT[, "time1"]
    Time2 <- SurvT[, "time2"]
    Time <- Time1
    Time[Time2 != 1] <- Time2[Time2 != 1]
    event <- SurvT[, "status"]
  } else {
    Time <- SurvT[, 1]
    event <- SurvT[, 2]
  }
  timesInd <- newdata2[[timeVar]] <= Tstart
  aliveThoriz <- newdata2[Time > Thoriz & timesInd, ]
  deadThoriz <- newdata2[Time <= Thoriz & (event == 1 | event == 3) & timesInd, ]
  indCens <- Time < Thoriz & (event == 0 | event == 2) & timesInd
  censThoriz <- newdata2[indCens, ]
  nr <- length(unique(newdata2[[idVar]]))
  idalive <- unique(aliveThoriz[[idVar]])
  iddead <- unique(deadThoriz[[idVar]])
  idcens <- unique(censThoriz[[idVar]])
  
  Surv.aliveThoriz <- if (is_counting) {
    survfitJM(object, newdata = aliveThoriz, idVar = idVar, M = M,
              survTimes = Thoriz, last.time = rep(Tstart, length(idalive)),
              LeftTrunc_var = all.vars(TermsT)[1L])
  } else {
    survfitJM(object, newdata = aliveThoriz, idVar = idVar, M = M,
              survTimes = Thoriz, last.time = rep(Tstart, length(idalive)))
    
  }
  Surv.deadThoriz <- if (is_counting) {
    survfitJM(object, newdata = deadThoriz, idVar = idVar, 
              survTimes = Thoriz, last.time = rep(Tstart, length(iddead)),
              LeftTrunc_var = all.vars(TermsT)[1L])
  } else {
    survfitJM(object, newdata = deadThoriz, idVar = idVar, 
              survTimes = Thoriz, last.time = rep(Tstart, length(iddead)))
  }
  Surv.aliveThoriz <- sapply(Surv.aliveThoriz$summaries, "[", 2)
  Surv.deadThoriz <- sapply(Surv.deadThoriz$summaries, "[", 2)
  if (nrow(censThoriz)) {
    Surv.censThoriz <- if (is_counting) {
      survfitJM(object, newdata = censThoriz, idVar = idVar, M = M,
                survTimes = Thoriz, last.time = rep(Tstart, length(idcens)),
                LeftTrunc_var = all.vars(TermsT)[1L])
    } else {
      survfitJM(object, newdata = censThoriz, idVar = idVar, M = M,
                survTimes = Thoriz, last.time = rep(Tstart, length(idcens)))
    }
    tt <- Time[indCens]
    weights <- if (is_counting) {
      survfitJM(object, newdata = censThoriz, idVar = idVar, M = M,
                survTimes = Thoriz, last.time = tt[!duplicated(censThoriz[[idVar]])],
                LeftTrunc_var = all.vars(TermsT)[1L])
    } else {
      survfitJM(object, newdata = censThoriz, idVar = idVar, M = M,
                survTimes = Thoriz, last.time = tt[!duplicated(censThoriz[[idVar]])])
    }
    Surv.censThoriz <- sapply(Surv.censThoriz$summaries, "[", 2)
    weights <- sapply(weights$summaries, "[", 2)
  } else {
    Surv.censThoriz <- weights <- NA
  }
  
  # until here I used the prederrJM.mv function from the JMbayes package
  
  newdata.id <- newdata[tapply(row.names(newdata), newdata$id, tail, 1), ]
  
  statusCens <- rbinom(n = length(weights), size = 1, prob = weights)
  matCens <- data.frame(years = newdata.id$years[newdata.id$id %in% names(Surv.censThoriz)],
                         pred_prob = 1 - Surv.censThoriz, status = 1 - statusCens)
  
  # New part for calibration plot
  matDead <- data.frame(years = newdata.id$years[newdata.id$id %in% names(Surv.deadThoriz)], 
                         pred_prob = 1 - Surv.deadThoriz, status = 1)
  matAlive <- data.frame(years = newdata.id$years[newdata.id$id %in% names(Surv.aliveThoriz)], 
                         pred_prob = 1 - Surv.aliveThoriz, status = 0)
  if (include.cens == TRUE) {
    mat <- rbind(matDead, matAlive, matCens)
  } else {
    mat <- rbind(matDead, matAlive)
  }
  
  
  
  # Cox model calibration curve
  #  The complementary log-log transformation for the predicted probabilities 
  #  rather than the probabilities themselves.
  mat$cox.cll <- log(-log(1 - mat$pred_prob))
  # Use a conventional Cox proportional hazards model with restricted cubic 
  #  splines to model the relationship between  
  #  log(-log(1-pred)) and the log-hazard of the outcome
  calibrate.cox <- coxph(Surv(years, status) ~ rcs(cox.cll, 3), x = T,
                         data = mat) 
  # Based on the fitted model, an estimated probability of the occurrence of the
  #  outcome prior to time t_0 can be estimated for each value of pred
  predict.grid.cox <- seq(0.01,
                          0.99, length  = 100)
  predict.grid.cox.cll <- log(-log(1 - predict.grid.cox))
  
  predict.grid.cox.df <- data.frame(predict.grid.cox)
  predict.grid.cox.cll.df <- data.frame(predict.grid.cox.cll)
  
  names(predict.grid.cox.df) <- "cox"
  names(predict.grid.cox.cll.df) <- "cox.cll"
  
  predict.calibrate.cox <- 1 - predictSurvProb(calibrate.cox,
                                               newdata = predict.grid.cox.cll.df,
                                               times = Thoriz)
  
  # Save results
  out <- list(x = predict.grid.cox, y = predict.calibrate.cox)
  
  if (pl == TRUE) {
    plot(predict.grid.cox, predict.calibrate.cox, type="l", lty=1, col="red",
         xlim=c(0,1),ylim=c(0,1),
         xlab = "Predicted probability",
         ylab = "Observed probability")
    abline(0, 1, lwd = 6, col = gray(0.85))
  }
  out
}
 

© Eleni-Rosalina Andrinopoulou