Joint Longitudinal-Survival Models
Shared-parameter models that estimate a repeated biomarker trajectory and time-to-event process together, allowing the event risk to depend on an error-prone latent biomarker history and informative dropout.
On this page
A biomarker is often measured intermittently and stops being measured after death or progression. A joint model estimates the noisy trajectory and event process together instead of treating the last observed value as error-free and ignoring why later measurements are missing.
Definition
A standard joint longitudinal-survival model combines a mixed model for repeated Y_i(t) with a survival submodel whose hazard depends on a prespecified feature of the latent trajectory: current value, slope, cumulative exposure, or shared random effects. The shared latent effects connect measurement error, irregular observations, and event risk. This is not a causal treatment model: treatment effects require an estimand, design, confounding control, and assumptions beyond joint likelihood specification.
Pros, cons, and trade-offs
Joint modeling can reduce bias from plugging noisy observed biomarker values into a time-dependent Cox model and can support dynamic risk prediction using a patient’s measurement history. It is demanding: longitudinal trajectory, association structure, baseline hazard, measurement error, and informative dropout assumptions can drive results. It differs from joint visit-process models, which explicitly model observation intensity; a biomarker-event joint model alone does not make visit timing ignorable.
When NOT to use — and when interpretation is misleading
Do not infer that lowering a biomarker causes lower event risk from an association parameter. Do not use post-event measurements, future data, or a model refit on outcome information to claim prospective prediction. Do not substitute a biomarker-event joint model for a recurrent-event, competing-risk, multistate, or visit-intensity model. If biomarker ordering is sparse or clinically incidental, transparent landmark analysis may be more credible.
Data-source caveats
EHR lab times need specimen collection time, units, assay changes, and clinically plausible windows; use collection time for the biological trajectory and result-release time to establish availability for prediction. A specimen collected on day 89 but released on day 92 is unavailable for a day-90 prediction. Claims rarely supply the repeated biomarker. Registry data may have scheduled measures but missed visits must be retained. If testing frequency responds to worsening health, model or sensitivity analysis for the visit process is needed; the ordinary shared-random-effects model does not automatically solve informative observation.
Decision diagram
%%{init: {'themeVariables': {'fontSize': '16px'}, 'flowchart': {'nodeSpacing': 18, 'rankSpacing': 24}}}%%
flowchart TD
A["Training cohort<br/>Biomarkers and event times"] --> B["Fit latent trajectory<br/>and linked event model"]
B --> C["Check model assumptions<br/>and observation process"]
C --> D["Freeze fitted model"]
D --> E["New patient at landmark<br/>Event-free, with results<br/>already available"]
E --> F["Predict future event risk"]
F --> G["Later outcomes assess<br/>calibration and discrimination"]Worked example
Scenario
Synthetic eGFR is observed before a 12-month kidney-failure horizon. The example shows preparation and prediction discipline, not a causal treatment effect.
Dataset
Two patients have measurements collected and released by day 90; only measurements available by the landmark may enter a day-90 prediction.
| person_id | day | egfr | event_day | event |
|---|---|---|---|---|
| 201 | 0 | 60 | 240 | 1 |
| 201 | 90 | 54 | 240 | 1 |
| 202 | 0 | 60 | 365 | 0 |
| 202 | 90 | 59 | 365 | 0 |
Steps
Result
The example supports a prediction workflow only. The steeper observed decline is not proof that changing eGFR would causally prevent kidney failure.
Trade-offs
Code example
Execution not verified · inspect inputs and dependencies before use
Primary fitting route using a mixed model and Cox model passed to JM. The association and prediction horizon must be prespecified and model diagnostics reported.
library(nlme); library(survival); library(JM)
set.seed(13); n <- 200; ids <- 1:n
baseline <- data.frame(id=ids, age=rnorm(n,67,8), sex=rbinom(n,1,.45))
baseline$event <- rbinom(n,1,plogis(-.3 + .25*baseline$sex))
baseline$event_day <- ifelse(baseline$event==1, runif(n,120,365), 365)
baseline$b0 <- rnorm(n,0,4); baseline$b1 <- rnorm(n,0,.025)
long <- merge(baseline['id'], data.frame(day=c(0,45,90)), by=NULL)
long <- long[long$day < baseline$event_day[match(long$id, baseline$id)], ]
ix <- match(long$id, baseline$id)
long$egfr <- 65 + baseline$b0[ix] + (-.05 + baseline$b1[ix])*long$day + rnorm(nrow(long),0,3)
lmeFit <- lme(egfr ~ day, random = ~ day | id, data=long)
coxFit <- coxph(Surv(event_day, event) ~ age + sex, data=baseline, x=TRUE)
jmFit <- jointModel(lmeFit, coxFit, timeVar="day")
summary(jmFit) # association, fit, calibration, and prediction checks remain required
Execution not verified · inspect inputs and dependencies before use
Runnable preparation and landmark guardrail. It does not fit a joint model; common Python survival APIs do not provide the same validated shared-parameter fit.
import pandas as pd
landmark = 90
long = pd.DataFrame({'id':[201,201,202,202,203], 'day':[0,90,0,90,89],
'available_day':[0,90,0,90,92], 'egfr':[60,54,60,59,55]})
usable = long.loc[(long.day <= landmark) & (long.available_day <= landmark)].copy()
assert 203 not in usable.id.values # day-89 specimen was released after the landmark
print(usable.groupby('id').size()) # export validated longitudinal and event tables to a joint-model engine
Execution not verified · inspect inputs and dependencies before use
Runnable data preparation for a validated external joint-model implementation; this code does not claim PROC PHREG fits a shared-parameter joint model.
data joint_long;
input id specimen_day available_day egfr;
datalines;
201 0 0 60
201 90 90 54
202 0 0 60
202 90 90 59
203 89 92 55
;
if not missing(specimen_day) and not missing(available_day) and specimen_day <= 90 and available_day <= 90; /* landmark-safe prediction inputs */
run;
proc sort data=joint_long; by id specimen_day; run;
/* Export long table plus one event row/person to a validated joint-model engine. */