The Bayesian flagship of cpaic. The relative effect of every treatment is the sum of its component effects, estimated jointly from individual patient data (IPD) and aggregate data (AgD). Aggregate arms are fitted by integrating the individual-level model over each study's covariate distribution, averaging the outcome on its natural scale (not the link scale). Because disconnected sub-networks share component parameters, the network is connected by construction.
Usage
cmlnmr(
ipd,
agd,
effect_modifiers,
inactive = NULL,
sep.comps = "+",
family = "binomial",
margins = NULL,
study = ".study",
trt = ".trt",
outcome = ".y",
time = ".time",
exposure = ".exposure",
start = ".start",
entry = ".entry",
r = "r",
n = "n",
E = "E",
se = "se",
cut_points = NULL,
interval = ".interval",
baseline = c("piecewise", "mspline"),
n_basis = 6L,
cor = NULL,
n_int = 64L,
QR = FALSE,
trt_effects = c("fixed", "random"),
re_parameterization = c("noncentered", "centered"),
prior_intercept_sd = 2.5,
prior_aux_sd = 1,
prior_beta_sd = 2.5,
prior_sigma_sd = 2.5,
prior_reg_sd = 1,
prior_gamma_dist = c("normal", "student_t"),
prior_gamma_scale = 1,
prior_gamma_df = 4,
prior_tau_dist = c("half-normal", "half-student-t"),
prior_tau_scale = 1,
prior_tau_df = 4,
prior_predictive = FALSE,
backend = c("rstan", "cmdstanr"),
chains = 4L,
iter_warmup = 500L,
iter_sampling = 500L,
seed = NULL,
adapt_delta = NULL,
max_treedepth = NULL,
...
)Arguments
- ipd
Individual patient data (one row per patient).
- agd
Aggregate data (one row per arm) with the per-study covariate summaries
x_mean(andx_sdfor normal margins) for each effect modifierx.- effect_modifiers
Character vector of effect-modifier names.
- inactive, sep.comps
Component coding (see
cpaic_network()).inactive = NULLgives the unanchored component parameterization, in which every unit receives its own parameter (Wigle & Béliveau 2022).- family
One of
"binomial","gaussian","poisson","survival".- margins
Optional named character vector giving the integration margin of each effect modifier:
"normal","bernoulli","gamma","lognormal", or"beta". The last three are set from the study mean and SD by method of moments (gamma/lognormalneed a positive mean;betaneeds a mean in(0, 1)andsd^2 < mean(1 - mean)). Defaults to Bernoulli for 0/1 covariates and normal otherwise.- study, trt
Column names (in both
ipdandagd).- outcome
IPD outcome column: 0/1 (binomial), numeric (gaussian), count (poisson), or the event indicator for survival.
- time, exposure
Outcome-time column for survival in both IPD and AgD; IPD exposure column for Poisson outcomes. If the Poisson exposure column is absent, every patient is given an exposure of 1 (equal follow-up) and a message says so, matching
cstc()andcmaic(), which drop the log offset when no exposure column is named.- start, entry
Survival columns giving the lower endpoint for interval-censored outcomes and the delayed-entry time. Missing columns imply zero.
- r, n, E, se
Aggregate columns: events
r, sample sizen(binomial), exposureE(poisson), meanoutcomeand its standard errorse(gaussian).- cut_points
Survival only: interior interval boundaries for a piecewise baseline.
NULLgives the exponential model. This argument is ignored for a continuous M-spline baseline.- interval
Retained for source compatibility; exact survival data do not use interval-indexed event counts.
- baseline
Survival baseline hazard:
"piecewise"(default, free step heights) or"mspline"(a continuous cubic M-spline with its exact integrated basis).- n_basis
Number of cubic M-spline basis functions. Must be at least 4.
- cor
Optional covariate correlation matrix for the Gaussian-copula integration. Must be a positive-definite correlation matrix (unit diagonal). Defaults to the within-study IPD correlation. For gamma, lognormal, or beta margins the auto-estimated correlation is only an approximation to the latent copula correlation; supply
coron the latent scale to control it exactly.- n_int
Integration points per aggregate arm (ignored for
gaussian, which is exact at the covariate means).This is the main cost lever for the survival families. An aggregate survival arm is supplied as reconstructed pseudo-IPD, so the aggregate likelihood is evaluated once per (aggregate row x integration point): the work grows as
nrow(agd) * n_int, and the default of 64 is expensive on a trial with several hundred reconstructed patients. Sampling is usually well behaved on the fixed-effects model (no divergences in the fixed-effects checks here), though the random-effects survival model can still produce a few divergent transitions and occasional rejected simplex proposals; inspect the diagnostics rather than assuming they are clean. If a survival fit is slow, reducen_intbefore suspecting the geometry. Refit at severaln_intvalues and compare the requested effects.plot_integration_error()does not support survival likelihoods.- QR
Logical scalar. If
TRUE, apply the scaled thin QR reparameterization used bymultinmato the complete fixed-effects design matrix. This is only a reparameterization: it must not change the posterior distribution, only the geometry the sampler explores. The default isFALSE, matchingmultinma.Do not turn this on expecting a free improvement. On the component networks tested here the fixed-effects design was not badly conditioned (a condition number near 19, in a network where every active treatment shared a component), and
QR = TRUEgave fewer effective samples per second thanQR = FALSE, with no divergent transitions either way. The intuition that a component design must be severely collinear, because one component recurs across many multi-component treatments, is not borne out: the study intercepts and the spread of the integration points keep the conditioning mild. CheckZ_condon the fit, and reach forQR = TRUEonly when it is large.- trt_effects
Treatment-effect model:
"fixed"or"random".- re_parameterization
Random-effects parameterization. The default
"noncentered"should be used for inference;"centered"is provided for sampling diagnostics.- prior_intercept_sd, prior_beta_sd, prior_reg_sd
Standard deviations for study-intercept, component-effect, and prognostic-regression normal priors.
- prior_aux_sd
Scale of the half-normal prior on the baseline-hazard smoothing parameter (survival families only). Each study has its own baseline hazard, given a first-order random-walk prior on the log spline coefficients with this shared smoothing scale. This is a simplified relative of the smoothing prior in
multinma, not the same prior: smaller values shrink every study's baseline toward equal spline weights, which is a smooth default shape and not a constant hazard. The default of 1 follows the Stan recommendation of a half-normal(0, 1) prior for a hierarchical scale.- prior_sigma_sd
Scale of the half-normal prior on the Gaussian residual standard deviation (gaussian family only), kept separate from
prior_beta_sdso the treatment-effect prior and the residual-noise prior are independent.- prior_gamma_dist, prior_gamma_scale, prior_gamma_df
Distribution, scale, and degrees of freedom for interaction priors. The Student t option uses the stated degrees of freedom.
- prior_tau_dist, prior_tau_scale, prior_tau_df
Distribution, scale, and degrees of freedom for the positive heterogeneity prior.
- prior_predictive
If
TRUE, sample from the prior and omit the observed likelihood. Replicated outcomes remain available forprior_predictive_check().- backend
Sampler engine:
"rstan"(default) or"cmdstanr". The two fit the same Stan models and are interchangeable; see the section below.- chains, iter_warmup, iter_sampling, seed
Sampler settings.
iter_warmupanditer_samplingare counted separately whichever backend is used; the translation to rstan's combinediteris handled internally.- adapt_delta, max_treedepth
Sampler tuning, or
NULLfor the engine default. These are named arguments rather than left to...because the two backends take them in different places.- ...
Further arguments for the sampler. These are passed through untouched and are therefore backend-specific: they reach
rstan::sampling()or thecmdstanr$sample()method as given. Prefer the named arguments above for anything that has one.Under
backend = "rstan"an argument rstan does not accept is rejected by name before the fit, rather than being handed torstan::sampling()to kill every chain with a message that names nothing. About twenty cmdstanr sampler arguments have no rstan equivalent (step_size,metric,inv_metric,adapt_engaged,parallel_chains,save_latent_dynamics, and so on), and a misspelled argument is caught the same way.
Value
An object of class cpaic_mlnmr with the fitted Stan object, the
component design, and a tidy table of component effects.
Details
The model includes component x effect-modifier interactions gamma, so the
average conditional link-scale treatment effect depends on target means:
$$\theta_t(x) = C_t' (\beta + \Gamma x).$$
The component main effects beta are the effects at the covariate origin
(x = 0) and are not marginally standardized quantities. Use newdata in
relative_effects() / component_effects() to obtain average conditional
link-scale effects at named target effect-modifier means. Use
marginal_effects() with an explicit target covariate distribution to
standardize treatment-specific outcomes before forming marginal contrasts.
Supported families: "binomial" (logit), "gaussian" (identity),
"poisson" (log), and "survival".
Integration
Aggregate covariates are integrated with Sobol' quasi-Monte-Carlo points
coupled by a Gaussian copula, whose correlation is pooled within IPD
studies on the Fisher z scale (or supplied via cor). Each covariate is
pushed through its own marginal inverse CDF: margins may be "normal"
(using x_mean and x_sd) or "bernoulli" (using x_mean as the
prevalence). Margins default to Bernoulli for covariates that are 0/1 in the
IPD and normal otherwise; a normal margin on a binary covariate would
integrate over a population that cannot occur.
Random effects
trt_effects = "random" adds study-arm deviations around the
component-implied relative effects. Deviations use a non-centered
parameterization by default. Within a multi-arm study, deviations relative
to the study baseline have the standard NMA correlation of 0.5. The
heterogeneity standard deviation tau has a half-normal(0, 1) prior by
default. The centered parameterization is available only to reproduce
sampling comparisons.
Priors
Defaults follow the Stan prior-choice recommendations. Component effects
use normal(0, 2.5), component by effect-modifier interactions use
normal(0, 1), study intercepts use normal(0, 2.5), and tau uses
half-normal(0, 1). Interaction priors do real regularization when Gamma is
weakly identified, so every fitted object records the complete prior
specification. Use prior_sensitivity() to quantify contrast movement and
prior_predictive = TRUE with prior_predictive_check() to inspect prior
implications before fitting the likelihood.
Survival
Survival uses the exact individual likelihood ported from multinma
(Phillippo et al. 2020). The model evaluates a hazard basis and its
integrated cumulative-hazard basis at every outcome, interval start, and
delayed-entry time. It supports observed events, right censoring, left
censoring, interval censoring, and delayed entry. baseline = "piecewise"
gives a piecewise-exponential baseline; baseline = "mspline" gives a
continuous cubic M-spline baseline.
Aggregate survival input must contain reconstructed event and censoring rows
with the same outcome-time columns as IPD, plus repeated arm-level covariate
summaries. The likelihood of every aggregate row is averaged over its
covariate integration points with log_sum_exp. Aggregate event counts and
person-time alone cannot recover this likelihood and are rejected explicitly.
Two qualifications, so that "exact" is not read more broadly than it should be.
The likelihood is exact; the covariate integration is not. Every individual contribution (event, right, left and interval censoring, delayed entry) is the exact analytic expression, verified against closed form to machine precision. The aggregate likelihood, however, averages that exact contribution over a finite quasi-Monte-Carlo grid of
n_intcovariate points, so it carries an integration error that shrinks withn_intbut is not zero. Increasen_intand confirm that the estimates are stable before relying on them. (The cruder alternative of summarizing an aggregate arm by its event count and person-time was biased by 36% in a two-group example, which is why it is rejected outright rather than offered as a fallback.)Each study has its own baseline hazard shape. Every study carries its own set of spline (or step) coefficients, smoothed toward a common shape by a shared random-walk scale (
prior_aux_sd), so the treatment effects do not have to absorb baseline misfit. A single global spline basis is built from the pooled follow-up range, so a study with much shorter follow-up may not inform the coefficients of the latest basis functions; those are then determined by the smoothing prior rather than by that study's data.
With baseline = "mspline" the sampler may print, during warmup,
coefficients[...] is not a valid simplex. sum(...) = nan. The smoothing
scale is unbounded above, so an early proposal can drive the random walk on
the log spline coefficients past the floating-point range and the softmax
returns NaN. Stan rejects that proposal and adaptation continues; this is
the rejection mechanism doing its job, not a fitted-model problem. Read it as
a warning only if it persists past warmup, which would indicate a genuinely
ill-conditioned baseline.
Within-study versus ecological effect modification
A single Gamma multiplies the individual covariates of the IPD and the
covariate means of the aggregate arms. These are not the same parameter. Write
the effect as
$$\alpha + \gamma_W (x - \bar x_s) + \gamma_B \bar x_s .$$
An aggregate contrast depends only on alpha + gamma_B xbar_s, so it carries
no information about the within-study interaction gamma_W; fitting one
Gamma imposes gamma_W = gamma_B. Randomization identifies each study's
treatment effect but does not randomize covariate means across studies, so a
between-study gradient is confounded in a way a within-study slope is not
(Berlin et al. 2002; Freeman et al. 2018).
The practical consequence: an interaction supported only by aggregate arms is
an ecological association being read as effect modification.
estimable_effects_at() separates the two in its identified_by column
("IPD" versus "aggregate") and marks the latter basis = "first-order screen"; cpaic_ranks() drops such elements from a hierarchy by default.
Treat a target-mean effect that leans on aggregate-identified
interactions as exploratory, and check it with prior_sensitivity().
Survival status coding
cmlnmr() uses the four-level convention 0 right-censored, 1 observed
event, 2 left-censored, 3 interval-censored. This is not the coding
cstc() and cmaic() use. The two-stage network constructor accepts only
0 for right censoring and 1 for an event, and rejects all other codes.
Do not reuse one status column across the two layers without recoding it.
Scope and current limitations
Two gaps are worth naming for anyone comparing this with multinma.
Conditional and marginal reporting are distinct.
relative_effects()defaults to the average conditional link-scale contrast at target means,(C_t - C_u)'(beta + Gamma x). Linearity inxmakes evaluation atE[X]equal to that average conditional contrast.marginal_effects()instead integrates treatment-specific outcomes over an explicit target distribution and forms contrasts within each posterior draw. Binomial, Poisson rate-difference, and survival measures additionally transport a selected study intercept or baseline hazard. Nonlinear marginal effects are treatment-level quantities and are not component additive.Every effect modifier enters both the prognostic terms and the full set of component interactions. There is no prognostic-only covariate role (unlike
cstc(), which separatesprognostics), so a covariate that shifts outcomes without modifying any component effect still adds interaction parameters that the data must then constrain toward zero.The Gaussian model has one residual standard deviation for the whole network. A single
sigmacovers every individual-level observation in every study and arm. Studies whose residual variance genuinely differs are then weighted mostly by sample size rather than by precision, so their relative contribution to a conflicting component effect, and the width of the resulting interval, are not right. Fit the families separately, or rescale, if the residual scales are far apart.Poisson aggregate arms assume exposure is independent of the covariates. The aggregate mean is
E * mean(exp(eta))over the integration points, which equals the correctsum_i E_i exp(eta_i)only when individual person-time is unrelated to the effect modifiers. When longer-followed patients differ systematically the aggregate contribution is biased; the interface has no way to accept exposure-weighted covariate moments.The copula correlation for a non-normal margin is approximate. For a Bernoulli margin the observed-to-latent map is multinma's closed-form
cor_adjust = "pearson"adjustment, which does not use the prevalences: at a prevalence of 0.1 a requested observed correlation of 0.5 comes back as about 0.42. For gamma, lognormal, and beta margins the observed correlation is used unadjusted and a warning says so. Supplycoron the latent scale to set it exactly.
Identifiability
A relative effect is uniquely estimable only if its component contrast lies
in the row space of the within-study component design (Wigle et al. 2026);
relative_effects() returns NA otherwise rather than a prior-driven
number. Note this checks identification of beta; a component x
effect-modifier interaction is additionally identified only by covariate
variation on the contrasts that involve it, and interactions informed only
by aggregate arms are weakly identified (prior_gamma_scale regularizes).
Backends
backend = "rstan" is the default. Its models are compiled when cpaic is
installed, so nothing else is needed and the examples and tests run anywhere.
backend = "cmdstanr" fits the identical models with CmdStan, which tracks
Stan releases more closely and is often faster, but it needs the cmdstanr
package and a separate CmdStan installation.
The two are interchangeable, not identical: they do not share a random number
stream, so the same seed gives different draws on each. Convergence
diagnostics are computed the same way for both (through posterior), so
rhat, ess_bulk, and ess_tail mean the same thing whichever produced the
fit, and everything downstream of the fit works on either.
They also differ in how they run chains. cmdstanr runs all chains at once.
rstan follows the R convention of taking its core count from
getOption("mc.cores"), which is 1 unless you set it, so on the default
backend the chains run one after another until you do:
Do not reach into fit$fit to summarize parameters. That slot holds whatever
the backend returned, an S4 stanfit or an R6 CmdStan object, and the two
share no accessors, so code written against one fails on the other. Use
posterior_summary(), which returns the same table either way.
References
Phillippo DM, Dias S, Ades AE, et al. (2020). Multilevel network meta-regression for population-adjusted treatment comparisons. JRSS A, 183(3), 1189–1210.
Wigle A, Beliveau A, Nikolakopoulou A, Lin L (2026). Creating Treatment and Component Hierarchies in Component Network Meta-Analysis.
Examples
# \donttest{
ipd <- data.frame(.study = "S1",
.trt = rep(c("Placebo", "A"), each = 100),
.y = rbinom(200, 1, 0.5), x1 = rnorm(200))
agd <- data.frame(.study = "S2", .trt = c("Placebo", "A+B"),
r = c(40, 55), n = c(100, 100),
x1_mean = c(0.2, 0.2), x1_sd = c(1, 1))
fit <- cmlnmr(ipd, agd, effect_modifiers = "x1", inactive = "Placebo",
chains = 2, iter_warmup = 200, iter_sampling = 200)
#> Warning: Bulk Effective Samples Size (ESS) is too low, indicating posterior means and medians may be unreliable.
#> Running the chains for more iterations may help. See
#> https://mc-stan.org/misc/warnings.html#bulk-ess
#> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable.
#> Running the chains for more iterations may help. See
#> https://mc-stan.org/misc/warnings.html#tail-ess
# Average conditional link-scale effects at target mean x1 = 0.2:
relative_effects(fit, newdata = data.frame(x1 = 0.2))
#> Relative effects (OR, natural scale)
#> Average conditional link-scale effect at target means: x1 = 0.2
#> treatment comparator estimate estimate_link se_link lower upper scale pr_gt0
#> A Placebo 2.121 0.711 0.286 1.141 3.557 natural 0.995
#> A+B Placebo 2.020 0.656 0.312 1.037 3.438 natural 0.975
#> basis
#> exact
#> first-order screen
#> `se_link` is on the log-ratio scale; the interval is back-transformed.
#> basis "first-order screen" = estimable by the row-space criterion but leaning
#> on aggregate arms or a survival baseline, so it can be optimistic; check
#> with prior_sensitivity() / estimable_effects_at().
# }