Skip to contents

This vignette shows how to export structured results from interactmeta for quality control, reporting, or downstream tables. The idea matches “calculate once, use many times”: fit the model, then take all display numbers from tidy() (and comparison helpers), not from re-typing forest-plot labels.

Getting started with cams(): vignette("cams").

Setup and fits

We use the steroids IV/NIV example (same data path as vignette("cams")). CAMS uses method = "REML" here; sensitivity analyses add DA, AD, and SWADA.

library(interactmeta)
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union

steroids_obs <- steroids |> filter(n > 0)
steroids_es <- escalc(
  measure = "OR",
  ai = treat.events,
  bi = treat.total - treat.events,
  ci = cont.events,
  di = cont.total - cont.events,
  data = steroids_obs
)
des <- ifrac(steroids_es, reference = "NIV")

fit_cam <- cams(des, reference = "NIV", method = "REML")
fit_da <- da(des, reference = "NIV", method = "REML")
fit_ad <- ad(des, reference = "NIV", method = "REML")
fit_sw <- swada(des, reference = "NIV", method = "REML")

Canonical export: tidy(fit)

tidy(fit) binds interaction, modifier, combined, and (optionally) heterogeneity rows in one tibble. You can still call tidy(interactions(...)) when you need a single estimand. Typical columns:

Column Meaning
estimate, std.error, conf.low, conf.high Point and interval on the requested scale
type e.g. Overall, Prediction
subgroup / level Modifier label ( modifiers() )
interaction Contrast label ( interactions() )
parameter, label Heterogeneity summaries ( heterogeneity() )
results <- tidy(
  fit_cam,
  estimands = c("interaction", "modifiers", "combined", "heterogeneity"),
  type = "Overall",
  exponentiate = TRUE
)
knitr::kable(
  results |>
    filter(estimand != "heterogeneity", type == "Overall") |>
    select(estimand, level, interaction, type, estimate, conf.low, conf.high),
  digits = 2
)
estimand level interaction type estimate conf.low conf.high
interaction IV-NIV IV-NIV Overall 1.35 0.32 2.38
modifier IV NA Overall 1.47 0.78 2.77
modifier NIV NA Overall 0.38 0.18 0.82
combined NA NA Overall -0.16 -1.72 1.39

Add run metadata once (reference, inference, reporting IF) for audit trails:

meta <- tibble::tibble(
  package = "interactmeta",
  method = "CAMS",
  inference = "REML",
  reference = "NIV",
  scale = "OR",
  reporting_if = as.numeric(ifrac(fit_cam, ifrac = "interaction"))
)
knitr::kable(meta, digits = 3)
package method inference reference scale reporting_if
interactmeta CAMS REML NIV OR 0.593

Pre-specified sensitivity analyses

sensitivity() fits DA, AD, SWADA, and CAMS on the same data; compare() aligns them on one table—useful as sensitivity rows in a report (not as a single “consensus” estimate).

sens <- sensitivity(des, reference = "NIV", method = "REML", silent = 2)
cmp_ix <- compare(
  sens,
  estimand = "interaction",
  exponentiate = TRUE,
  type = "Overall"
)
knitr::kable(cmp_ix, digits = 2, caption = "Interaction OR: construction sensitivity")
Interaction OR: construction sensitivity
approach contrast estimate se ci.lb ci.ub
DA IV - NIV (DA) 1.96 0.86 0.83 4.62
AD IV - NIV 3.86 2.02 1.38 10.78
SWADA IV - NIV 3.86 2.02 1.38 10.78
CAMS IV - NIV 1.35 0.52 0.32 2.38
cmp_sg <- compare(
  sens,
  estimand = "modifiers",
  exponentiate = TRUE,
  type = "Overall"
)
knitr::kable(cmp_sg, digits = 2, caption = "Modifier ORs: DA vs SWADA vs CAMS")
Modifier ORs: DA vs SWADA vs CAMS
approach subgroup estimate se ci.lb ci.ub
DA NIV 0.41 0.16 0.19 0.88
SWADA NIV 0.38 0.15 0.17 0.84
CAMS NIV 0.38 0.15 0.18 0.82
DA IV 0.79 0.15 0.55 1.15
SWADA IV 1.47 0.49 0.76 2.83
CAMS IV 1.47 0.47 0.78 2.77

Note. Under default weights, SWADA matches AD for the interaction; both appear in the interaction sensitivity table because they answer different construction definitions.

QC: one row lookup vs the printed summary

QC should recover the same Overall interaction as print() / interactions():

from_tidy <- cmp_ix |>
  filter(approach == "CAMS") |>
  pull(estimate)
from_api <- tidy(
  fit_cam,
  estimands = "interaction",
  type = "Overall"
)$estimate
stopifnot(
  length(from_tidy) == 1L,
  length(from_api) == 1L,
  isTRUE(all.equal(from_tidy, from_api, tolerance = 1e-6))
)

Handoff to layout tools

interactmeta does not render regulatory TLGs. After tidy(), use your table package of choice (gt, gtsummary, flextable, …) on the tibble. Keep computation (this export) separate from layout, in line with CDISC Analysis Results Data thinking: structured statistics first, formatted output second.

Minimal gt example (skip if gt is not installed):

results |>
  filter(estimand != "heterogeneity", type == "Overall") |>
  select(estimand, level, estimate, conf.low, conf.high) |>
  gt::gt() |>
  gt::fmt_number(columns = c(estimate, conf.low, conf.high), decimals = 2)

Forest plots and exported numbers

Pooled Overall diamonds on forestplot(fit_cam) match modifiers() / combined() at the reporting IF shown under the axis. When QC fails, check exponentiate, type, and reference match between the plot and tidy() calls.

forestplot(
  fit_cam,
  exponentiate = TRUE,
  favours = c("Lower mortality", "Higher mortality")
)