Multilevel Unanchored Meta-Regression for Indirect Treatment Comparisons
Overview
Bayesian multilevel unanchored meta-regression (ML-UMR) for indirect treatment comparisons using individual patient data (IPD) and aggregate data (AgD). Implements shared prognostic factor assumption (SPFA) and relaxed SPFA models for binary, continuous, count, and time-to-event (survival) outcomes via 'Stan', including parametric and flexible M-spline survival baselines. Also provides a one-arm simulated treatment comparison (STC) via comparator-population G-computation and naive unadjusted benchmarks. ML-UMR is an adaptation of the ML-NMR methodology (Phillippo et al. 2020, doi:10.1111/rssa.12579) implemented in the 'multinma' package (GPL-3) to the unanchored two-trial case. The public API uses familiar names for data setup, integration and effect summaries, but ML-NMR and ML-UMR specifications and fitted objects are not interchangeable because their evidence structures and models differ.
Time-to-event outcomes
- Feature: New
"survival"outcome family.set_ipd()accepts time-to-event data with right, left, interval and delayed-entry censoring through asurvival::Surv()object ortime/status/entry_timecolumns, andset_agd_surv()takes the comparator arm as reconstructed pseudo-IPD plus its covariate moments.outcomeis not required for this family. - Feature: Parametric and flexible baselines through the
distributionargument ofmlumr(): proportional hazards ("exponential","weibull","gompertz"), accelerated failure time ("exponential-aft","weibull-aft","lognormal","loglogistic","gamma","gengamma") and flexible hazards ("mspline","pexp") with a random-walk smoothing prior.make_knots()places spline knots;knotsaccepts a custom set.
Show all changes in 0.2.0Hide the rest of the changes in 0.2.0
- Feature: The baseline hazard is estimated per study by default (
aux_by = ".study"), each stratum with its own knots over its own observed support, asmultinma::nma()does.aux_by = "none"shares one baseline across studies. - Feature: New priors
prior_aux,prior_aux2(second generalized-gamma auxiliary) andprior_smooth, withdefault_prior_aux()anddefault_prior_smooth(). - Feature:
predict()on a survival fit returns"survival","hazard","cumhaz","rmst","median"and"loghr"curves.timespicks evaluation times (snapped to the fitted grid, with a message when they move),pred_timessets the grid,"median"carriesp_not_reached, and RMST rows carry theirhorizon.conditional_effects()andconditional_predict()give covariate-conditional versions. - Feature:
marginal_effects()reports survival effects on the natural scale: the hazard ratio (HR) for proportional-hazards fits, the time ratio (TR) for shared-shape AFT fits, the exponentiated contrast (EXP_DELTA_ETA) otherwise, plus the RMST difference (RMSTD) and ratio (RMSTR). Marginal hazard ratios carry anat_timecolumn because they are time-varying and non-collapsible;predict(type = "loghr")gives the whole curve. - Improvement: An effect a fit cannot supply is an error naming the alternative, not a substitution:
effect = "tr"on a proportional-hazards fit,effect = "hr"on an AFT fit, and either on a relaxed or study-specific-shape AFT fit. - Feature: Survival benchmarks.
naive()returns the unadjusted Cox log hazard ratio and refuses a partial likelihood with no maximum or no convergence.stc()returns the RMST difference by parametric G-computation withflexsurv, with a bootstrap interval (n_boot, default 200) and anrmst_horizonargument. - Feature:
survival_unitoncalculate_loo(),calculate_waic()andcompare_models()chooses what one held-out unit is for the reconstructed comparator:"observation"(default, optimistic),"arm"or"aggregate". - Improvement: Coefficient rows in
summary()are labeled by covariate name (beta[age],beta_index[age],beta_comparator[age]); the storedvariablestrings are unchanged. - Fix: Interval- and left-censored likelihoods under delayed entry are evaluated in whichever of the difference, increment or quadrature forms keeps its digits, and the exponential, Weibull, Gompertz and log-logistic use closed forms. See
?mlumr. - Fix:
mlumr()refuses an M-spline basis column that no subject was at risk under, judged over the merged[entry, exit]intervals of each study, and refuses a shared baseline (aux_by = "none") whose studies never overlap on a spline column. See?mlumr. - Improvement:
predict(type = "rmst")andpredict(type = "median")warn when the prediction grid is too coarse to trust and point at a finern_rmst_gridorpred_times. - Improvement:
?set_agd_survstates that reconstruction uncertainty is not propagated and which population the covariate moments must describe under delayed entry.
Transport to any target population
- Feature: A
newdataargument onmarginal_effects()andpredict()standardizes effects and absolute predictions to an arbitrary target population by Bayesian g-computation, for every family. Standardizing to the index covariates reproducespopulation = "index"exactly. Survival RMST effects and absolute predictions transport; the marginal hazard ratio is reported for a target too, with itsat_time. - Improvement:
marginal_effects()on a relaxed fit queried for the index population prints a one-line note with the marginal posterior variance change ofbeta_comparatorper covariate, descriptive only. Silence it withoptions(mlumr.quiet_relaxed_index = TRUE).
Plotting
- Feature:
plot()methods formarginal_effects()(forest),predict()(curves with credible bands, or point-intervals for scalar types) andconditional_effects()(effects by profile). Each forest draws the null its measure implies, uses a log axis for ratio measures, and reads its interval level from the result. - Feature:
geom_km()overlays the observed Kaplan-Meier curves on a model survival plot, colored by treatment, honoring delayed entry, one panel per population. It draws right-censored data only. - Feature:
plot_prior_posterior()overlays each named parameter's posterior on the prior the fit recorded for it. - Feature:
mlumr_forest()draws a forest from a plain data frame, for mixed comparisons such asnaive(),stc()and both ML-UMR models on one axis; a single very wide interval is clipped with an arrow instead of squeezing the rest. - Improvement:
marginal_effects(),predict()andconditional_effects()return lightweightdata.framesubclasses so the methods dispatch; data frame behavior is unchanged.
Identification of the relaxed comparator coefficients
- Feature:
check_identification()reports, before fitting, whether the aggregate subgroup rows can identifybeta_comparatorinmodel = "relaxed": the row count againstK + 1, and the balance, effective dimension and spread of the centered subgroup-mean matrix, with a descriptive-only label for nonlinear links. - Feature:
prior_beta_comparatorargument tomlumr()sets a separate prior onbeta_comparator, including a different family. Defaults toprior_beta; ignored formodel = "spfa"; reported byprior_summary()and swept byprior_sensitivity(). - Fix: The weak-identifiability warning no longer counts a duplicated
set_agd()row as evidence; it triggers on the rank of the mean design under the identity link and on the number of distinct integration grids otherwise.
Data preparation and integration
- Fix:
distr()honors arguments passed by position.distr(qnorm, 10, 2)previously integrated a standard normal without any message. Positional and abbreviated arguments are now matched against the quantile function's formals at construction, and an argument matching nothing is an error. - Feature: Moment-parameterized
qgamma()/pgamma()/dgamma()andqlogitnorm()/plogitnorm()/dlogitnorm()acceptmeanandsd, sodistr(qgamma, mean = age_mean, sd = age_sd)works as printed in a baseline table. Without those arguments they forward tostats. See?GammaDistand?logitNormal. - Fix:
set_agd()no longer rejects a valid binary covariate SD. The bound was the population SDsqrt(p (1 - p)); it is now the finite-sample maximum with a rounding allowance. See?set_agd. - Improvement:
set_agd()requiresoutcome_nwhen normal aggregate data have more than one row, and the multi-row comparator estimand for the normal family is the sample-size-weighted mean of the strata rather than an inverse-variance average.naive()andstc()use the same weights. - Improvement:
set_ipd()warns about a rank-deficient or nearly collinear covariate design and names the near-redundant covariates. Autoscaling of a covariate with no usable empirical scale falls back to the unscaled prior with a warning. - Fix:
add_integration()rejectscor_adjust = "pearson"with non-Gaussian margins, warns about a nonbinary discrete margin (count or ordered category) that the copula correction does not cover, and warns when a supplieddistr()grossly contradicts the declaredset_agd()moments. - Improvement:
check_integration()compares realized and target correlations on the same method (Pearson or Spearman), reports fidelity of each grid margin to the declared mean and SD, lists which correlation pairs it could and could not measure (correlation_pairs, with"partial"and"review"verdicts), withholds the comparison undercor_adjust = "none", and reports a comparison it could not make as"unavailable"instead of"close".
Priors and sensitivity
- Breaking: for
family = "normal"the default priors are on the outcome's scale. Under the identity link the intercepts and coefficients are in the outcome's units, and the residual SD is under either link, so the fixednormal(0, 10)intercept and half-normal(0, 2.5) residual-SD defaults were informative for an outcome such as a 0 to 100 score or a blood pressure in mmHg. They pulled each intercept toward zero, the comparator's (resting on one aggregate mean) the most, which moved the mean difference, and they understated the residual SD, which narrowed the intervals. Withsd(y)the IPD outcome SD, the defaults are nownormal(0, 10 * sd(y))for the intercepts andnormal(0, 2.5 * sd(y))for the coefficients (identity link), and a half-normal with scale2.5 * sd(y)for the residual SD (either link);autoscale = TRUEmultiplies a coefficient's scale bysd(y)as well as dividing it bysd(x)under the identity link. Priors written out in full are used as given.prior_summary()prints the scales the model used, andprior_sensitivity()sweeps a default or autoscaledprior_betain the same outcome-SD units. Refit normal-family analyses that relied on the defaults or onautoscale. - Fix:
prior_normal(autoscale = TRUE)rescales the prior location as well as the scale, so a nonzero prior mean is on the covariate's own scale. - Improvement:
prior_sensitivity()varies the prior and nothing else: every model-defining setting is replayed from the fit and...may not override it. Relaxed fits sweep the comparator prior alongside the index one (prior_beta_comparator_scales, reported inscale_comparator), survival rows carryeffectandat_time, and a refit that cannot replay a sampler argument the original fit used (such asthinorinit) warns and names it. - Breaking:
prior_sensitivity()quantile columns are namedq2.5,q50,q97.5, matchingmarginal_effects(), rather thanq2andq98. Duplicateprobsare refused. - Improvement:
prior_sensitivity()no longer claims a scale sweep proves the inference is data-driven; its interpretation text points atcheck_identification(). - Fix:
prior_summary()names constrained priors correctly: a truncated normal or t with a nonzero location is not a half-normal or half-t, and an exponential is not truncated at all.
Benchmarks: naive() and stc()
- Breaking:
stc()no longer reports an index-population contrast. That estimate assumed a constant link-scale effect, which population adjustment exists to avoid.stc()returns the comparator-population estimand only; usemlumr()when the index population is the target. - Breaking: A normal-family
stc()under a log link reports the log mean ratio in$estimateand the mean difference in$md,$md_se,$md_lower,$md_upper. Under the identity link the two coincide. - Fix:
stc()refuses a binomial outcome model with complete separation and, whendetectseparationis installed, tests exactly for quasi-complete separation; the result records aseparationstatus. A Poisson model with no events is refused the same way. See?stc. - Fix: Directly observed arm proportions and rates in
naive(), and the observed comparator proportion in a binomialstc(), use exact Clopper-Pearson and Garwood intervals instead of Wald intervals, which excluded the truth at zero counts. Contrasts remain Wald. - Fix:
naive()combines several aggregate rows as strata; the comparator standard error is that of the size-weighted mean of the row proportions. - Fix:
naive()andstc()apply a continuity correction only to an observed proportion of 0 or 1 instead of clamping every proportion, which slightly changes the reported effect for arms with no events or no non-events.
Fitting, diagnostics and model comparison
- Breaking:
mlumr()gains model-defining arguments ahead ofchains,iterand the other sampler controls. Pass sampler settings by name. - Improvement:
mlumr()centers covariates by default (center = TRUE), which removes the intercept-versus-slope collinearity that pushed the sampler into deep trajectories on raw-scale covariates. Reported contrasts are unchanged;prior_interceptthen applies at the pooled covariate mean.qr = TRUEoffers a QR-rotated design instead. - Improvement: The binary, continuous and count IPD likelihoods use Stan's fused GLM densities on their canonical links; results agree with 0.1.0 to Monte Carlo error.
- Fix: With the cmdstanr engine,
fit$summary$se_meanis the Monte Carlo standard error of the posterior mean fromposterior::mcse_mean(). It was the posterior SD over the square root of the bulk ESS, which is computed on rank-normalized draws and misstates the error for skewed quantities such as a ratio. - Fix:
mlumr()refuses a normal fit whose outcome is constant or reproduced exactly by its covariates, where the posterior for the residual SD is improper, and warns when the design is saturated. - Fix: Marginal probabilities, means, rates and their contrasts are formed on the log scale; the
safe_logit()andsafe_divide()clamps are gone, so ratios with a near-zero comparator are no longer understated andlor_*is finite where it used to hit the clamp. A ratio beyond double precision isInf. See?mlumr-numerical-evaluation. - Breaking:
predict(type = "link")returns the marginal linkg(E[g^-1(eta)])rather than the mean linear predictorE[eta]; the two agree for the identity link only. This differs frommultinmaon purpose, since every effect mlumr reports is population-standardized. - Improvement:
seed = NULLuses the documented default of 2026 with a warning instead of drawing an unrecorded seed from the session RNG. - Improvement:
verbose = FALSEsilences the cmdstanr sampler banner as well. - Fix: A caller's rstan
controllist is merged with the backend's instead of failing argument matching. - Fix: A cmdstanr run that produced no draws reports how many chains wrote nothing and where CmdStan's messages are, instead of failing inside
checkmateon a temporary path. - Fix: The cmdstanr executable cache key now covers every
#include, the CmdStan version and its build flags; a changed include no longer reuses a stale executable. Expect one recompile after upgrading. - Fix:
check_diagnostics()and the fit summary no longer drop an infinite Rhat before taking the maximum, report missing or non-numeric diagnostics as unknown rather than as zero, and compute tail ESS. - Improvement:
predict(),marginal_effects(),conditional_effects()andconditional_predict()warn once per call whenNAorNaNdraws were dropped from a summary. - Fix:
conditional_predict()names quantile columns from the requested probabilities, so non-roundprobsno longer returnNA. - Fix:
calculate_loo()refusesmoment_match = TRUE, whichlooignores for a matrix. - Fix:
compare_models()with"loo"or"waic"refuses fits whose stored outcomes differ.loo_compare()pairs the pointwise scores by position, so the same data fitted in another row order gave a wrongse_diff. Thecalculate_loo()andcalculate_waic()results carry the outcomes asloo'syhash, soloo::loo_compare()warns on the same mismatch. - Improvement: The
compare_models()printout no longer presentsse_diff > 2as a decision rule.
E
The NEWS section is longer than shown here.
From NEWS.md in the release
Install
Health
- OK13 OK2026-08-05
- NOTE12 OK, 1 NOTE2026-08-01
- OK13 OK2026-07-29
- ERROR12 OK, 1 ERROR2026-07-27
- OK13 OK2026-06-09
Show 2 earlier changesHide 2 earlier changes
- ERROR12 OK, 1 ERROR2026-06-08
- OK2 OK2026-05-21
Documentation
- Help pages
- 52 (2 internal)includes a package overview page
- Examples that run
- 27%9 run in a plain check · 24 never run
- Exported functions with examples
- 81%
- Documented parameters
- 93%
- Return-value docs
- 100%
- Help pages citing references
- 4% with a bibliography file
- Aggregate subgroup information for the relaxed model HTML
- Binary outcomes: an unanchored PASI 75 comparison HTML
- Choosing a method for single-arm indirect comparisons: ML-UMR, STC, and naive HTML
- Continuous outcomes: an unanchored mean-difference comparison HTML
- Count outcomes: an unanchored rate-ratio comparison HTML
- Fitting, priors, and diagnostics HTML
- Introduction to mlumr HTML
- Preparing data and integration points HTML
- Survival (time-to-event) outcomes HTML
Downloads
Repository
- PRs merged (all time)
- 93%
- Median days to close, 50 most recently updated closed issues
- not measured
- Median days to close, 50 most recently updated closed PRs
- 0
Stars over time
Forks over time
Releases over time
PRs over time
Commits over time
18 development-tooling and community-health practices detected across 8 families in the upstream repository
Checks run against github.com/choxos/mlumr on 2026-10-22.
The R CMD check workflow runs on Linux, macOS and Windows
GitHub Pages site last deployed 2026-10-13 · The repository has a site deploy workflow
pkgdown site last built 2026-10-13
Show all practicesHide 4 practices
This repository has an AI review tool set up.
- CodeRabbitSeen in the repository: .coderabbit.yaml
Dependencies
Installing mlumr adds 52 packages (26 with compiled code, 47 MB of source tarballs). Heaviest: BH 14 MB, ggplot2 6.0 MB, loo 2.7 MB.
Code & Tests
Datasets
People & History
Pick two releases to compare their code metrics.
- 0.2.0Latest2026-10-13 · current release · diff ↗ ·
added 15 exports
check_diagnostics, check_identification, default_prior_aux, default_prior_smooth, dgamma, dlogitnorm, geom_km, make_knots, mlumr_forest, pgamma, plogitnorm, plot_prior_posterior, qgamma, qlogitnorm, set_agd_surv - 0.1.02026-05-20
- RR 4.6.0 released · 2026-04-24
Package metadata
- First published
- 2026-05-20
- Total releases
- 2 / 1 yrs
- License
- GPL-3 OSI
- Additional repositories
- mc-stan.org
- Minimum R
- ≥ 4.1.0
- Bundled data
- 12 KB / 9 files
- Download size
- 1.8 MB
Cite
Cite this package
Run in R for the authors' preferred citation:
citation("mlumr")The package provides a citation file that builds its entry when R runs it. Run citation() to see it.
The repository also keeps a CITATION.cff.
Cite the R Observatory
For a number measured here: a download total, a coverage figure, an archival date.
From data release v2026-10-23, which the citation names so these numbers can be found later. More on citing and the projects behind them.