In progressive diseases, patients are expected to deteriorate over time.
When deterioration cannot be reversed or cured, interventions on the disease aim to mitigate decline or slow progression.
Traditional statistical estimates may not capture the impact of a treatment on a patient’s life.
\(\alpha_\text{placebo}(t)\): natural cubic spline fitted to placebo response evaluated at time \(t\).
\[ \begin{aligned} \alpha_\text{treatment}(t) - \alpha_\text{treatment}(0) = (\alpha_\text{placebo}(t) - \alpha_\text{placebo}(0)) \cdot (1 - \beta) \end{aligned} \]
\(\beta\) = reduction in decline since baseline (relative to placebo)
\[ \begin{aligned} \alpha_\text{treatment}(t) = \alpha_\text{placebo}\left( t \cdot (1 - \beta) \right) \end{aligned} \]
\(\beta\) = slowing of disease progression since baseline (relative to placebo)
Non-proportional PMRMs use visit-specific \(\beta_1, \ldots, \beta_J\).
Slow execution:
library(nlme)
system.time({
fitted_pmrm <- gnls(
model = custom_pmrm_formula,
data = clinical_data,
params = list(
v0 + v1 + v2 + v3 + v4 + v5 + v6 ~ 1,
as.formula(paste0('k ~ ', covariates, ' + 0')),
coefficient_formula
),
correlation = corSymm(form = ~ as.numeric(visit_sequence) | USUBJID),
weights = varIdent(form = ~ 1 | visit_sequence),
start = start_coefficients,
control = gnlsControl(nlsTol = tolerance)
)
})
#> user system elapsed
#> 513.851 99.188 491.208Frequent errors:
https://openpharma.github.io/pmrm
Fits the decline and slowing PMRMs (proportional and non-proportional).
Runs fast and reliably thanks to the {RTMB} package by Kasper Kristensen.
Analyst-friendly interface with familiar S3 methods.
Part of CRAN and the OpenPharma GitHub organization (like packages mmrm, DoseFinding, and RobinCar2).
Likelihood ratio tests for model comparison (e.g. tests of non-proportionality).
Significance tests of individual decline/slowing parameters.
Allow arbitrary parameterization the decline/slowing parameters (not just proportional vs non-proportional).
library(pmrm)
data <- pmrm_simulate_slowing_proportional(
patients = 100,
# Visit times inform covariance matrix and default spline knots:
visit_times = c(0, 12, 24, 36, 52, 64, 76),
# Placebo mean response at each time point:
alpha = c(50, 44, 38, 32, 30, 28, 25),
# Slowing in placebo (always 0) and treatment arms, respectively:
beta = c(0, 0.3),
# Visit-specific residual standard deviations:
sigma = rep(3, 7),
# Latent parameters for residual correlations among visits:
rho = rep(1, 7 * (7 - 1) / 2),
# Standard deviation of observed times around scheduled visits:
tau = 3
)#> # A tibble: 700 × 11
#> patient visit arm i j k y t beta mu e
#> <chr> <ord> <ord> <int> <int> <int> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 patient_1 visit_1 arm_1 1 1 1 51.8 0 0 50 1.78
#> 2 patient_1 visit_2 arm_1 1 2 1 42.4 17.4 0 41.4 0.973
#> 3 patient_1 visit_3 arm_1 1 3 1 42.7 19.4 0 40.4 2.33
#> 4 patient_1 visit_4 arm_1 1 4 1 33.0 38.2 0 31.4 1.58
#> 5 patient_1 visit_5 arm_1 1 5 1 32.3 50.3 0 30.1 2.18
#> 6 patient_1 visit_6 arm_1 1 6 1 27.6 70.1 0 26.5 1.10
#> 7 patient_1 visit_7 arm_1 1 7 1 28.8 73.3 0 25.7 3.10
#> 8 patient_2 visit_1 arm_2 2 1 2 52.2 0 0.3 50 2.18
#> 9 patient_2 visit_2 arm_2 2 2 2 49.8 7.93 0.3 47.2 2.63
#> 10 patient_2 visit_3 arm_2 2 3 2 43.0 27.7 0.3 40.4 2.61
#> # ℹ 690 more rows
#> Model:
#>
#> PMRM type: slowing
#> Parameterization: proportional
#>
#> Fit:
#>
#> Convergence: converged
#> Observations: 700
#> Parameters: 36
#> Log likelihood: -1301.719
#> Deviance: 2603.438
#> AIC: 2675.438
#> BIC: 2839.277
#>
#> Treatment effects:
#>
#> estimate std.error
#> arm_2 0.300327 0.01234619
#> # A tibble: 2 × 6
#> parameter arm estimate standard_error lower upper
#> <chr> <ord> <dbl> <dbl> <dbl> <dbl>
#> 1 beta arm_1 0 NA NA NA
#> 2 beta arm_2 0.300 0.0123 0.276 0.325
#> # A tibble: 4 × 7
#> arm visit time estimate standard_error lower upper
#> <ord> <ord> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 arm_1 visit_1 0 50.4 0.318 49.8 51.1
#> 2 arm_1 visit_2 12 44.3 0.308 43.7 44.9
#> 3 arm_1 visit_3 24 38.2 0.330 37.6 38.9
#> 4 arm_1 visit_4 36 32.1 0.320 31.5 32.8
✅ {pmrm} recovers true parameters across a range of scenarios: https://openpharma.github.io/pmrm/articles/validation.html.
✅ Full coverage unit testing with testthat to ensure:
An efficient toolkit for fitting complex statistical models in R (Kristensen (2026)).
Developed by Kasper Kristensen at Danish Technical University.
Key features:
✅ Fast, exact, high-order automatic differentiation
✅ Fast flexible Laplace approximation of marginal likelihoods for models with many random effects.
✅ Uses R’s native syntax for model specification.
%%{init: {"theme": "default", "themeVariables": {"fontSize": "40px"}, "flowchart": {"nodeSpacing": 30, "rankSpacing": 150, "padding": 20}} }%%
graph BT
A["CppAD"] --> C
B["Matrix"] --> C
C["TMB"] --> D
D["RTMB"]
{RTMB} = Native R syntax for {TMB} models (Kristensen (2026)).
{TMB} = Template Model Builder (Kristensen et al. (2016)).
{CppAD} = automatic differentiation in C++, runs exact high-order derivatives (Bell (2005)).
{Matrix} = numerical linear algebra in R, including sparse matrices (Bates et al. (2025)).
\[ \begin{aligned} y_1, \ldots, y_n \stackrel{\text{iid}}{\sim} \text{Normal}(\mu, \ \sigma^2) \end{aligned} \]
library(RTMB)
data <- ChickWeight
objective <- function(parameters) {
# Optional: attach list elements to objective function environment:
getAll(parameters, data)
# Optional: decalre the response variable to enable extra RTMB features:
y <- OBS(weight)
# Calculate the minus log likelihood using R syntax:
-sum(dnorm(y, alpha + beta * Time, 1, log = TRUE))
}#> alpha beta
#> 27.467425 8.803039
#> # A tibble: 3 × 10
#> variable mean median sd mad q5 q95 rhat ess_bulk
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 alpha 27.5 27.5 0.0802 0.0819 2.73e1 2.76e1 1.00 1053.
#> 2 beta 8.80 8.80 0.00627 0.00624 8.79e0 8.81e0 1.00 1105.
#> 3 lp__ -436638. -436638. 1.06 0.727 -4.37e5 -4.37e5 1.000 1363.
#> # ℹ 1 more variable: ess_tail <dbl>
objective <- function(constants, parameters) {
getAll(parameters, constants)
y <- OBS(y)
# Spline fitted to placebo respones:
spline <- splinefun(x = spline_knots, y = alpha, method = "natural")
# Covariate adjustment with sparse matrices (Matrix R package):
adjustment <- (X %*% gamma) - sum(X_column_means * gamma)
# Vector of fitted responses:
mu <- spline((1 - beta[arm]) * time) + adjustment
# Unstructured covariance among visits:
sigma <- diag(exp(phi))
Sigma <- sigma %*% unstructured(n_visits)$corr(rho) %*% sigma
# Multivariate normal likelihood (on the minus log scale):
target <- 0
for (patient in seq_len(n_patients)) {
visits <- seq(to = patient * n_visits, length.out = n_visits)
target <- target - RTMB::dmvnorm(y[visits], mu[visits], Sigma, log = TRUE)
}
target
}https://wlandau.github.io/mbsw2026/example.R
103 lines of code
Progression models for repeated measures (PMRMs) provide meaningful estimates for patients.
The {pmrm} package fits PMRMs robustly and efficiently.
The backend package {RTMB} runs fast reliable models in native R syntax.
© 2026 Eli Lilly and Company