Skip to contents

Fits the CAMS model (Panaro, Röver & Friede 2025) for consistent inference on treatment effect modification from modifier-level aggregate data (vignettes often use classic subgroup tables). Modifier-level treatment effects are inputs and derived outputs; the methodological target is not a separate meta-analysis within each modifier level.

Usage

cams(
  x,
  reference = NULL,
  modifier = NULL,
  trial = NULL,
  subgroup = NULL,
  trial_var = "study",
  subgroup_var = "level",
  contrast = "all",
  contrast_sep = "-",
  big_var = 100^2,
  method = "Bayesian",
  tau.prior = NULL,
  tau.interaction.prior = NULL,
  fixed.prior = NULL,
  fit_args = list(),
  precompute = FALSE,
  lazy_latent = TRUE,
  silent = 2,
  seed = getOption("interactmeta.seed", 42L),
  ...
)

Arguments

x

A metafor::escalc object, a data frame with yi / vi, or output from ifrac() (must include ifrac_* design columns). Also accepts a data frame built from trial-level columns (trial, subgroup, yi, vi).

reference

Subgroup label used as reference in ifrac() and in the model. Required when x is not already an ifrac() result.

modifier

Column that holds the two levels of the within-trial contrast (default "subgroup", so the shipped examples keep working). Overrides subgroup and subgroup_var when set. Level labels stay as stored. Display headings capitalize the first letter of this name (dose is shown as Dose).

trial, subgroup

When x is escalc / a data frame, column names passed to ifrac() (defaults "trial" and "subgroup"). subgroup remains accepted for the default column name; prefer modifier for the argument.

trial_var, subgroup_var

Column names used in the model (defaults match trial and subgroup). subgroup_var remains accepted.

contrast, contrast_sep, big_var

Passed to ifrac() when information fractions are computed inside cams(). To append trial-level combined or interaction contrast rows to an ifrac() table, call ifrac() with include_combined / include_interaction before fitting.

method

Inferential method. "Bayesian" (default) fits with bayesmeta. Any value accepted by metafor::rma() for its method argument fits both orthogonal frequentist blocks (e.g. "REML", "ML", "DL", "PM", "EB", "SJ", "HE" / "CO", "HS", "HSk", "EE", "GENQ", "PMM", "GENQM"). Aliases "bayes" and "bayesian" match "Bayesian". "frequentist" selects "REML".

tau.prior, tau.interaction.prior

Heterogeneity priors for \(\tau_m\) (overall) and \(\tau_g\) (interaction), passed to bayesmeta. Defaults when both are omitted match bayesmeta: improper "uniform" on both blocks (see Priors). "uniform" and "sqrt" are the improper bayesmeta densities. A half-normal or half-Cauchy is a density, for example function(tau) bayesmeta::dhalfnormal(tau, scale = 0.5). If tau.interaction.prior = NULL and the user supplied tau.prior, the interaction prior inherits tau.prior.

fixed.prior

Normal prior for the bayesmeta fixed effects, as c(mean = 0, sd = 10). NULL uses that default (needed for a stable bmr overall block with improper tau; not bayesmeta's improper flat \(\mu\)). Applied as independent normals on \((\alpha,\theta)\) and on \(\gamma\), not as an independent normal on the reconstructed \(\beta=\theta-\gamma\). "uniform" is the improper flat prior.

fit_args

List passed to the backend (bayesmeta quadrature or metafor::rma()). Model choices such as priors do not belong here. For Bayesian fits, optional n_support (integer) is forwarded to both bayesmeta::bmr() and bayesmeta::bayesmeta() as n.support (fewer \tau nodes → smaller DIRECT product grid).

precompute

If TRUE, build the default parameter matrix and warm the effect cache before returning (legacy eager behaviour). Default FALSE: print.interactmeta(), summary.interactmeta(), and tidy() trigger reporting work on first use.

lazy_latent

For Bayesian fits, skip per-trial convolve() marginals at fit time (default TRUE). Trial-level $latent objects are built when shrinkage or other code needs them. Set FALSE for the full latent at fit.

silent

Sampler verbosity; default 2 suppresses messages.

seed

Random seed for reproducible draws (default from option "interactmeta.seed", currently 42L). Set to NA for a non-reproducible draw. If seed is also passed in ..., a warning is given and this argument wins.

...

For method = "Bayesian", bayesmeta quadrature options (e.g. delta, interval.type). For metafor methods, additional rma() arguments (e.g. test, control). Merged into fit_args when not already set.

Value

A CAMS fit with class "interactmeta" plus attribute "interactmeta" (plot settings and ifrac_data for forestplot()).

Details

cams() is the fitter.

By default, ifrac() is run inside the fitter on the input (e.g. a metafor::escalc object). You can also pass an existing ifrac() tibble.

For two subgroups per trial, CAMS fits an information-fraction meta-regression of combined coordinates \(m_j\) and a random-effects meta-analysis of within-trial contrasts \(g_j\) (Panaro, Röver & Friede 2025, §3.4). Default inference is Bayesian (bayesmeta); method = "REML" (or other metafor::rma() methods) gives frequentist inference.

Sampling assumption (within-trial covariance)

Within each trial, \(g_j=y_{Bj}-y_{Aj}\) with \(\mathrm{Var}(g_j)=v_{Aj}+v_{Bj}\) and information fraction \(\pi_j=(1/v_{Bj})/(1/v_{Aj}+1/v_{Bj})\) (IV weight of the non-reference subgroup). That choice makes \(\mathrm{Cov}(m_j,g_j)=0\) when \(\mathrm{Cov}(y_{Aj},y_{Bj})=0\). This package currently assumes that within-trial subgroup sampling covariance is zero. Separate overall and interaction fits (bmr/bayesmeta or two rmas) rely on that orthogonality. If subgroup estimates share a control arm or other within-trial dependence, both \(\mathrm{Var}(g_j)\) and separate fits for \(m_j\) and \(g_j\) are misspecified unless \(\pi_j\) and the variances are redefined with the covariance (not implemented here).

Model

cams() fits the comparison CAMS model with free ecological / composition slope \(\beta\) (b_ifrac): population lines \(\mu_A=\alpha\), \(\mu_B=\alpha+\gamma+\beta\), \(M(\pi)=\alpha+\pi\beta+\gamma\).

Inference

CAMS can be fitted using Bayesian or frequentist inference. These are not two different scientific models. Set method = "Bayesian" (default) for bayesmeta, or any metafor::rma() method for the two frequentist components (default frequentist choice is "REML"). See vignette("cams").

For Bayesian fits, bayesmeta options may go in fit_args or .... For metafor fits, further rma() arguments (e.g. test, control) may be passed in ... or fit_args (fit_args wins on conflicts).

swada(), da(), and ad() are not alternative CAMS models. SWADA does not use the CAMS information-fraction (IF) construction; IF quantities and IF-based plots are not available for SWADA objects. Comparison helpers may combine multiple user-supplied fits only when those fits are passed explicitly.

CAMS is the statistical model. da() (Difference of Averages) and ad() (Average Difference) are different constructions (Panaro, Röver & Friede 2026) for a treatment-by-subgroup interaction from subgroup-level aggregate data. They can use different study weights and need not agree. swada() applies the SWADA weighting principle so those analyses share one contribution vector; under the default Interaction RE-weights, the DA interaction equals AD. conventional() is a compatibility bundle of DA and AD. None of these is the 2002 van Houwelingen joint bivariate mixed model.

Method

method selects inference: "Bayesian" uses bayesmeta; values documented for metafor::rma() ("REML", "ML", "DL", "PM", …) use metafor on both components. The fit stores fit$method (e.g. "Bayesian", "REML"), fit$inference, and fit$support (DIRECT quadrature support or one-row metafor estimate). print.interactmeta() shows the same wording as the cams(..., method = ) argument (Bayesian, REML, …).

Priors

Bayesian CAMS passes priors through to bayesmeta. They are stored on fit$priors. Heterogeneity defaults match bayesmeta::bayesmeta():

fixed effects

independent \(\mathrm{Normal}(0, 10)\) on the overall-block means \((\alpha,\theta)\) and on the interaction mean \(\gamma\), with \(\theta=\beta+\gamma\). Stored b_ifrac is the reconstructed ecological slope \(\beta=\theta-\gamma\); that prior is not an independent \(N(0,10)\) on \(\beta\). (bayesmeta's improper flat \(\mu\) is not the CAMS default: the overall block is fit with bmr, where a proper normal is the reliable companion to improper tau)

tau.prior

improper uniform \(p(\tau_m)\propto 1\) (\(\tau_m \ge 0\)), bayesmeta's tau.prior = "uniform"

tau.interaction.prior

the same improper uniform on \(\tau_g\) when both heterogeneity priors are omitted. If tau.interaction.prior is NULL and the user supplied tau.prior, the interaction prior inherits that density

Character shorthands "uniform" and "sqrt" are the improper bayesmeta densities \(p(\tau)\propto 1\) and \(p(\tau)\propto \tau^{-1/2}\) (\(\tau \ge 0\)). They are not \(U(0, U)\). Frequentist method values ignore Bayesian priors (warning if supplied).

Empirical heterogeneity (not the package default). CDSR calibration suggests interaction heterogeneity is often substantially smaller than overall treatment-effect heterogeneity, so a common weakly informative half-normal on both blocks can be too diffuse for \(\tau_g\) (Panaro, Röver & Friede, arXiv:2606.23968). That paper reports measure-specific empirical scales (e.g. \(\mathrm{HN}(0.5)\)-style WIP for overall log-ratio / SMD-type endpoints in Röver et al., 2021; tighter interaction scales such as \(\mathrm{HN}(0.1)\) in sensitivity analyses and \(\mathrm{HN}(0.07)\) empirically for log-ROR). Vignettes set those half-normal densities explicitly; they are not silently applied by cams().

Fixed-effect parameterization: bayesmeta/bmr receive independent \(N(0, 10)\) on \((\alpha,\theta)\) (beta.prior.cov = diag(sd^2)) and mu.prior on \(\gamma\). That prior factors across the two orthogonal blocks. Independent normals on \((\alpha,\beta,\gamma)\) would couple \(\theta\) and \(\gamma\) a priori and are not what this engine uses.

Intervals

For Bayesian CAMS, public extractors (modifiers(), interactions(), combined(), predict_cams_by_if(), shrinkage()) report credible intervals from the DIRECT-node Gaussian mixture (.weight and within-node .cond_* moments). Default interval.type is "central" (equal-tailed); pass interval.type = "shortest" to cams() for the narrowest interval rule. Default combined() and interactions() at type = "Overall" with no exponentiate use stored bmr / bayesmeta intervals when possible; other requests still use the mixture grid. plot_contribution() uses central (equal-tailed) mixture intervals for ribbon limits by default (ribbon_interval.type). Alpha and theta are never convolved with each other.

References

Panaro RV, Röver C, Friede T (2025). Consistent Bayesian meta-analysis on subgroup specific effects and interactions. arXiv:2512.18785. https://doi.org/10.48550/arXiv.2512.18785

Panaro RV, Röver C, Friede T (2026). Subgroup comparisons within and across studies in meta-analysis. Research Synthesis Methods (in press). arXiv:2508.15531. https://doi.org/10.48550/arXiv.2508.15531

Panaro RV, Röver C, Friede T (2026). Empirical prior distributions for treatment-by-subgroup interaction heterogeneity in random-effects meta-analysis. arXiv:2606.23968. https://doi.org/10.48550/arXiv.2606.23968

Röver C, Bender R, Dias S, Schmid CH, Schmidli H, Sturtz S, Weber S, Friede T (2021). On weakly informative prior distributions for the heterogeneity parameter in Bayesian random-effects meta-analysis. Research Synthesis Methods, 12(4), 448–474. https://doi.org/10.1002/jrsm.1475

Examples

if (FALSE) { # \dontrun{
library(interactmeta)

es <- escalc(measure = "IRR", yi = edss$yi, vi = edss$vi, data = edss)
fit <- cams(es, reference = "Lower", method = "Bayesian")
fit_reml <- cams(es, reference = "Lower", method = "REML")
forestplot(fit)
plot_contribution(fit)
plot(fit)
} # }