Skip to content

Latest commit

 

History

24 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

mlumr: Multilevel Unanchored Meta-Regression mlumr logo

CRAN status CRAN downloads R-universe R-CMD-check pkgdown Lifecycle: maturing License: GPL v3

Archived on Software Heritage

mlumr implements Multilevel Unanchored Meta-Regression (ML-UMR), a population-adjusted indirect treatment comparison for disconnected evidence networks: individual patient data (IPD) for one treatment, aggregate data (AgD) for the comparator, and no common reference arm (e.g., comparing single-arm studies). It estimates treatment effects while adjusting for cross-trial differences in prognostic factors, extending Multilevel Network Meta-Regression (ML-NMR; Phillippo et al., 2020) to the unanchored setting.

It provides four estimators behind one data interface, two of them ML-UMR variants:

  • ML-UMR (Bayesian): the shared prognostic factor assumption (SPFA) model and the relaxed SPFA model, fitted via Stan with quasi-Monte Carlo integration and a Gaussian copula for population adjustment
  • STC (frequentist): one-arm simulated treatment comparison via comparator-population G-computation, with delta-method standard errors for binary, continuous, and count outcomes and a nonparametric bootstrap for time-to-event
  • Naive (benchmark): unadjusted comparison of crude outcome summaries

Binary, continuous, count, and time-to-event outcomes are supported.

Installation

# From CRAN
install.packages("mlumr")

# Development version
# install.packages("remotes")
remotes::install_github("choxos/mlumr")

mlumr fits models with Stan (default backend rstan, C++ generated by rstantools and compiled at install), so a C++ toolchain is required:

  • macOS: Xcode Command Line Tools (xcode-select --install)
  • Windows: Rtools
  • Linux: g++ and make (usually present by default)

A cmdstanr backend is optional; see Stan backend below.

Quick start

library(mlumr)
set.seed(2026)

# --- Prepare IPD (index treatment) ---
n_A <- 500
ipd_df <- data.frame(
  trt = "Drug_A",
  outcome = rbinom(n_A, 1, 0.55),
  age_group = rbinom(n_A, 1, 0.40),
  sex = rbinom(n_A, 1, 0.55)
)
ipd <- set_ipd(ipd_df, treatment = "trt", outcome = "outcome",
               covariates = c("age_group", "sex"))

# --- Prepare AgD (comparator) ---
agd_df <- data.frame(
  trt = "Drug_B", n_total = 400, n_events = 148,
  age_group_mean = 0.35, sex_mean = 0.50
)
agd <- set_agd(agd_df, treatment = "trt",
               outcome_n = "n_total", outcome_r = "n_events",
               cov_means = c("age_group_mean", "sex_mean"),
               cov_types = c("binary", "binary"))

# --- Combine and add integration points ---
dat <- combine_data(ipd, agd)
dat <- add_integration(dat, n_int = 64,
  age_group = distr(qbern, prob = age_group_mean),
  sex = distr(qbern, prob = sex_mean)
)

# --- Run all three methods ---
naive_result <- naive(dat)                          # instant
stc_result   <- stc(dat)                            # instant
fit <- mlumr(dat, model = "spfa",                   # Bayesian, a few minutes
             prior_intercept = prior_normal(0, 10),
             prior_beta = prior_normal(0, 2.5),
             chains = 4, iter = 4000, warmup = 2000, seed = 42)

summary(fit)
marginal_effects(fit, effect = "lor")

Supported outcome families and link functions

Family Outcome type Link functions
Binomial Binary (0/1) logit (default), probit, cloglog
Normal Continuous identity (default), log
Poisson Count + exposure log (default)
Survival Time-to-event log (proportional hazards, or accelerated failure time)

For survival outcomes the comparator arm is reconstructed pseudo-IPD (event/censoring times digitized from a published Kaplan-Meier curve) supplied via set_agd_surv(). The distribution argument selects one of nine parametric forms (exponential, Weibull, Gompertz, and accelerated failure time variants) or a flexible "mspline" / "pexp" baseline. predict() returns survival, hazard, RMST, and median curves plus the time-varying marginal log hazard ratio (type = "loghr"). marginal_effects() reports the hazard ratio for proportional-hazards distributions on the natural scale (null 1, as for the rate ratio). For accelerated failure time distributions it reports a time ratio only when the two studies share one baseline shape and one coefficient vector; under the default aux_by = ".study", or under the relaxed model, it reports the exponentiated linear-predictor contrast (EXP_DELTA_ETA) instead. Both are accompanied by the RMST difference and ratio, each reported with the restriction horizon it was integrated to.

The baseline hazard is estimated separately for each study (aux_by = ".study", the default, as in multinma). The spelling matches multinma; the meaning does not. In an anchored network a study-specific shape is a nuisance parameter and within-study randomization still identifies the treatment effect. Here each study contributes exactly one arm, so a study-specific shape and a treatment-specific shape are perfectly aliased: nothing in the data separates them. aux_by = "none" instead assumes one shared shape, which for proportional-hazards distributions imposes proportional hazards across studies and for accelerated failure time distributions imposes a common distributional shape; that assumption is at least testable against the two observed Kaplan-Meier curves. Neither choice is assumption-free, so fit both and report which was used.

After marginalization the hazard ratio is generally time-varying in the SPFA and relaxed models alike, because each arm weights the covariate distribution by its own survival and the two risk sets diverge; study-specific shapes add the ratio of the baselines on top of that. The scalar marginal hazard ratio is therefore its value at one time, chosen with at_time, and the primary reported estimand should be the loghr curve or the collapsible RMST effects. See vignette("survival-outcomes").

# Index IPD with a Surv outcome; comparator from a digitized KM curve
ipd <- set_ipd(ipd_df, treatment = "trt", covariates = c("age", "sex"),
               family = "survival", time = "time", status = "status")
agd <- set_agd_surv(km_df, treatment = "trt", time = "time", status = "status",
                    cov_means = c("age_mean", "sex_prop"),
                    cov_sds = c("age_sd", NA),
                    cov_types = c("continuous", "binary"))
dat <- add_integration(combine_data(ipd, agd), n_int = 64,
                       age = distr(qnorm, mean = age_mean, sd = age_sd),
                       sex = distr(qbern, prob = sex_mean))

fit <- mlumr(dat, model = "spfa", distribution = "weibull")
marginal_effects(fit, effect = "hr")     # marginal hazard ratio, null 1
predict(fit, type = "rmst")              # restricted mean survival time

Methods overview

Feature ML-UMR SPFA ML-UMR Relaxed STC Naive
Covariate adjustment Joint model Joint model Outcome regression None
Effect modification Assumed absent Estimated Not captured N/A
Uncertainty Posterior Posterior Delta method (bootstrap for TTE) Delta method
Population weighting QMC integration QMC integration Comparator-population G-computation Crude observed populations
Integration points required Yes Yes Yes, except normal identity No

When to use ML-UMR

Method Data required Type of ITC Pairwise only Type of treatment effect Target population
MAIC IPD + AgD Anchored or unanchored Yes Marginal Comparator
STC IPD + AgD Unanchored Yes Marginal Comparator
ML-NMR IPD + AgD Anchored No Marginal or conditional Any pre-specified target
ML-UMR IPD + AgD Unanchored Yes Marginal or conditional Any pre-specified target

The index population is the decision-relevant target in most health technology assessment (HTA) settings: cost-effectiveness models are built for the population a reimbursement decision is about, which is normally the index trial's. MAIC and pairwise STC identify an effect in the comparator population, so applying them to a decision problem carries an extra, usually implicit, transport step. ML-UMR makes that step explicit by standardizing each treatment model to the index population (or any pre-specified covariate target via newdata). marginal_effects() reports both populations; lead with the population relevant to the decision and show the other as a sensitivity analysis. This standardization is itself a transport step and relies on correct outcome models, the stated cross-treatment assumptions, and adequate covariate overlap.

ML-UMR is most appropriate when:

  1. You have IPD for one treatment and AgD for the comparator
  2. No common reference arm connects the evidence (unanchored)
  3. Binary, continuous, count, or time-to-event outcomes are of interest
  4. Covariate distributions differ between trial populations

Key functions

Function Purpose
set_ipd(), set_agd() Prepare IPD and AgD with outcome and covariate specification
set_agd_surv() Prepare comparator survival pseudo-IPD (reconstructed KM)
combine_data() Combine IPD and AgD into a unified dataset
add_integration() Generate QMC integration points with Gaussian copula
make_knots() Choose M-spline knots for a flexible survival baseline
mlumr() Fit ML-UMR SPFA or Relaxed model via Stan
mlumr_engine() Get or set the Stan backend (rstan or cmdstanr)
naive(), stc() Frequentist benchmark methods
predict() Population-specific predicted outcomes
marginal_effects() Posterior treatment effect summaries
conditional_effects() Covariate-conditional treatment effects
conditional_predict() Predictions at specific covariate values
check_identification() Screen whether the aggregate rows can inform the relaxed model's comparator coefficients (binomial, normal, poisson)
check_integration() Check that the integration points reproduce the declared AgD moments
prior_sensitivity() Refit across a grid of prior scales, varying only the prior
calculate_loo(), calculate_waic(), compare_models() Bayesian model comparison (LOO-CV, WAIC); survival_unit sets the pointwise unit for survival fits
calculate_dic() Deviance information criterion, computed from the fit's own log likelihood
plot(), mlumr_forest(), geom_km(), plot_prior_posterior() Forest, curve, and prior-versus-posterior figures

Stan backend

The default backend is rstan. Users who prefer cmdstanr can switch after installation:

mlumr_engine("cmdstanr")   # offers to install cmdstanr + CmdStan if needed
mlumr_engine("rstan")      # switch back
mlumr_engine()             # check current engine

The preference persists for the session. Set a permanent default in .Rprofile with options(mlumr.stan_engine = "cmdstanr"), or override per fit: mlumr(dat, model = "spfa", engine = "cmdstanr").

Using mlumr alongside multinma

Some exported functions intentionally use names familiar from multinma counterparts (set_ipd(), set_agd(), add_integration(), unnest_integration(), distr(), marginal_effects(), make_knots(), geom_km(), qbern()/pbern()/dbern(), qgamma()/pgamma()/dgamma(), qlogitnorm()/plogitnorm()/dlogitnorm()).

Shared names signal related concepts, not drop-in compatibility. The data objects, argument sets, supported evidence structures, and fitted models differ, so translate a multinma specification explicitly and check each mlumr help page. One difference is worth stating outright, because both packages spell it "link": multinma::predict(type = "link") returns the average linear predictor E[eta], and its marginal link-scale contrast is marginal_effects(mtype = "link"). mlumr's predict(type = "link") is the marginal one, the fitted link applied to the population-standardized response mean, because every effect mlumr reports is standardized over a population and there is no conditional estimand here for E[eta] to pair with. For survival, aux_by = NULL resolves to ".study"; the mlumr-specific aux_by = "none" shares one baseline shape across both studies.

The cleanest practice is still to use one package per session, matched to the network type (anchored connected → multinma; unanchored disconnected → mlumr). When both must be attached, disambiguate with the namespace prefix:

library(multinma)
library(mlumr)

# mlumr fits ML-UMR (disconnected, two-trial)
fit_umr <- mlumr::mlumr(dat, model = "spfa")

# multinma fits ML-NMR (connected network)
net_nmr <- multinma::set_ipd(pso_ipd, study = studyc, trt = trtc, r = pasi75)
fit_nmr <- multinma::nma(net_nmr, regression = ~ age:.trt)

Vignettes

Detailed tutorials are available as package vignettes, in reading order:

Getting started

  • vignette("introduction"): overview, the unanchored problem, the four methods, and a reading roadmap
  • vignette("data-preparation"): the four-step pipeline and the numerical integration that powers population adjustment

Outcome types (each a complete worked example)

  • vignette("binary-outcomes"): binary response (PASI 75, plaque psoriasis)
  • vignette("continuous-outcomes"): continuous outcome (shoulder pain, ASD vs exercise therapy)
  • vignette("count-outcomes"): count outcome (dmft dental caries, silver diamine fluoride vs nano-silver fluoride)
  • vignette("survival-outcomes"): time-to-event (progression-free survival in multiple myeloma, lenalidomide vs thalidomide)

Methods

  • vignette("fitting-and-diagnostics"): sampler control, backends, priors, and MCMC diagnostics
  • vignette("choosing-a-method"): assumptions, model comparison, and a decision guide for ML-UMR vs STC vs naive
  • vignette("subgroup-identification"): how many jointly defined aggregate subgroup rows the relaxed model needs, the geometry those rows must have, and how to read check_identification()

References

mlumr implements multilevel unanchored meta-regression (ML-UMR):

Chandler, C. & Ishak, J. (2026). "Anchors Away: Navigating Unanchored Indirect Comparisons with Multilevel Unanchored Meta-Regression (ML-UMR)." Preprint, arXiv:2606.20341 [stat.ME]. doi:10.48550/arXiv.2606.20341

Chandler, C. & Ishak, J. (2025). "Anchors Away: Navigating Unanchored Indirect Comparisons With Multilevel Unanchored Meta-Regression (ML-UMR)." ISPOR Europe 2025, MSR28. Value in Health, 28, S498. https://www.valueinhealthjournal.com/article/S1098-3015(25)05944-3/abstract

Chandler, C. & Ishak, J. (2026). "Surviving Unanchored Indirect Comparisons: An Extension of Multilevel Unanchored Meta-Regression (ML-UMR) for Survival Analyses." ISPOR 2026, MSR131. Value in Health, 29(S6). https://www.ispor.org/heor-resources/presentations-database/presentation-cti/ispor-2026/poster-session-3-3/surviving-unanchored-indirect-comparisons-an-extension-of-multilevel-unanchored-meta-regression-ml-umr-for-survival-analyses

ML-UMR is an adaptation of multilevel network meta-regression (ML-NMR) to the unanchored case, where no common comparator arm links the two studies:

Phillippo, D. M., Dias, S., Ades, A. E., Belger, M., Brnabic, A., Schacht, A., Saure, D., Kadziola, Z., & Welton, N. J. (2020). "Multilevel Network Meta-Regression for population-adjusted treatment comparisons." Journal of the Royal Statistical Society: Series A, 183(3), 1189--1210. doi:10.1111/rssa.12579

Citing mlumr

citation("mlumr")

Each release is archived on Zenodo with a citable DOI (the DOI badge above appears after the first release). The repository ships machine-readable citation and software metadata for FAIR harvesters:

  • CITATION.cff: Citation File Format (GitHub "Cite this repository", Zenodo).
  • codemeta.json: CodeMeta 2.0 software metadata crosswalk.
  • .zenodo.json: Zenodo deposit metadata (authors, ORCIDs, license, related identifiers).
  • ro-crate-metadata.json: RO-Crate 1.1 packaging and provenance.
  • inst/CITATION: the R-level citation (citation("mlumr")).

All carry the GPL-3.0 SPDX identifier, both authors' ORCIDs, and the upstream ML-NMR / multinma provenance.

Authors

Acknowledgments

mlumr is an adaptation of the GPL-3 licensed multinma package, reusing its Sobol quasi-Monte Carlo and Gaussian-copula integration machinery and adding Stan likelihoods for the unanchored two-treatment problem.

Portions of the package code, documentation, and Stan models were drafted and reviewed with the assistance of large language models: Anthropic's Claude Opus 4.6, 4.7, and 5 (via Claude Code) and OpenAI's ChatGPT 5.4, 5.5, and 5.6 Sol (via Codex). All methodological choices, design decisions, and the final review and validation were performed by the named authors, who take responsibility for the package's contents.

License

GPL-3. See https://www.gnu.org/licenses/gpl-3.0 for the full license text.

About

An R package for fitting multilevel unanchored meta-regression (ML-UMR) models in unanchored indirect treatment comparisons (uITCs).

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

12 stars

Watchers

4 watching

Forks

Releases

Used by

Contributors

Languages