Repository navigation
Support for constrained longitudinal data analysis (cLDA) parametrization #551
Description
Typically, MMRMs are parametrized to directly model the change from baseline as the outcome and include the baseline value itself as a covariate in the model.
An alternative is to have the baseline value as part of the response vector.
Then, in randomized clinical trials, we could assume two treatment groups (e.g. active and control) to have the same mean (or distribution more generally) at baseline, hence in a regression model share a common intercept.
This is known as constrained longitudinal data analysis (cLDA) in the literature (https://www.jstor.org/stable/25053123).
However, fitting a discrete-time cLDA using mmrm is difficult because the time/visits must be encoded as factors.
I try to illustrate the issue below with the fev_data example from the mmrm package.
The following code chunk fits an MMRM with the baseline visit as part of the response vector, but it has separate intercepts (estimates of the baseline mean) for the two groups:
library(mmrm)
library(emmeans)
library(data.table)
dt = as.data.table(fev_data)
# Drop columns not required for this example/illustration
for (j in c("RACE", "SEX", "WEIGHT", "VISITN2")) set(dt, j = j, value = NULL)
# Preparation of data for cLDA model
dt_bl = copy(dt)
dt_bl = dt_bl[VISITN == 1]
dt_bl[, `:=`(FEV1 = FEV1_BL, VISITN = 0, AVISIT = "VIS0")]
dt = rbindlist(list(dt, dt_bl))
dt[, AVISIT := factor(AVISIT, levels = sort(levels(AVISIT)))]
setorder(dt, USUBJID, AVISIT)
# Usual approach: Includes separate intercepts (baseline estimates)
m1 = mmrm(
FEV1 ~ 1 + AVISIT + ARMCD:AVISIT + us(AVISIT | USUBJID),
data = dt
)
summary(m1)One approach to circumvent this could be to do some manual work, e.g. by specifying each term in the model formula explicitly.
This works in principle for estimating the model, but working with it afterwards (e.g. using emmeans) is difficult.
# Construct formula by hand
form_mmrm = as.formula(paste(
"FEV1 ~ 1 + AVISIT +",
paste(
sprintf(
'I((ARMCD == "TRT") & (AVISIT == "%s"))',
dt[, levels(AVISIT)][-1]
),
collapse = " + "
),
"+ us(AVISIT | USUBJID)"
))
# Estimates the correct model, but difficulties arise when trying to work with emmeans afterwards.
m2 = mmrm(form_mmrm, data = dt)
summary(m2)
emmeans(
m2, ~ ARMCD | AVISIT, data = dt,
params = c("AVISIT", "ARMCD", '"TRT"', '"VIS1"', '"VIS2"', '"VIS3"', '"VIS4"')
)I know that this is not an issue/shortcoming of mmrm per se but more related to the R formula syntax and/or ecosystem in general.
Nonetheless, I thought since this problem/model is quite specific but may be of interest for many users of mmrm it could be worth to think about a solution for this within the mmrm package.
Activity
danielinteractive commentedon Nov 24, 2025
Thanks @DavidJesse21 - do you have an idea of an interface you would like to have here in
mmrm?DavidJesse21 commentedon Nov 25, 2025
Hi @danielinteractive , thank you for the quick response.
I am not sure what could be a "clean"/convenient solution as I am not familiar with the internals of
mmrm.One initial idea that came to my mind is to somehow specify in the formula to remove certain terms/columns in the design matrix, e.g. like
FEV1 ~ 1 + AVISIT + ARMCD:AVISIT - AVISITVIS0:ARMCDTRT + us(AVISIT | USUBJID).Edit:
Another idea for the interface (again via the formula):
FEV1 ~ 1 + AVISIT + constrained(ARMCD:AVISIT) + us(AVISIT | USUBJID).Here,
constrained()could/should, for example, signal that the first category ofAVISITshould be ignored in this interaction.Perhaps,
constrained()would then need to require further arguments to control what is happening here.