Skip to content

Repository files navigation

dagmv: causally disciplined multiverse analysis

Multiverse and specification-curve analyses pool model specifications that are causally incompatible. Some control sets identify the declared causal effect; others condition on mediators or colliders and identify nothing of the kind. dagmv disciplines the multiverse with a set of candidate causal graphs (DAGs) that encode rival assumptions about the contested controls. For each graph it determines which specifications are admissible adjustment sets, and it decomposes the spread of the multiverse into a within-DAG component (sampling and specification noise) and a between-DAG component (structural uncertainty). The share of the between-DAG component is reported as the structural share.

Installation

# development version from GitHub
remotes::install_github("sokubo/dagmv")

# or from a local checkout of this repository
# R CMD INSTALL .

The package needs only base R (>= 4.1). ggplot2 (plots), MASS (negative binomial engine) and fixest (panel engine) are optional.

Quick start

library(dagmv)

# Simulated data: educ confounds union and wage, occ is caused by union,
# exper affects wage only
set.seed(1)
n <- 5000
educ  <- rnorm(n)
exper <- rnorm(n)
union <- as.numeric(0.5 * educ + rnorm(n) > 0)
occ   <- 0.6 * union + 0.4 * educ + rnorm(n)
wage  <- 0.3 * union + 0.5 * occ + 0.4 * educ + 0.3 * exper + rnorm(n)
d <- data.frame(wage, union, occ, educ, exper)

# 1. Encode the disagreement as candidate graphs
g1 <- dag_parse("dag { occ -> union ; occ -> wage ; union -> wage ;
                       educ -> union ; educ -> wage ; exper -> wage }")
g2 <- dag_parse("dag { union -> occ ; occ -> wage ; union -> wage ;
                       educ -> union ; educ -> wage ; exper -> wage }")

# 2. What does each world say about each control?
ctrl <- c("occ", "educ", "exper")
mv_classify(g1, ctrl, exposure = "union", outcome = "wage")
mv_classify(g2, ctrl, exposure = "union", outcome = "wage")

# 3. Fit every specification once; map admissibility per world
fits <- mv_run(d, outcome = "wage", exposure = "union", controls = ctrl,
               dags = list(G1 = g1, G2 = g2))
fits

# 4. Decompose: how much of the disagreement is structural?
mv_decompose(fits)
mv_plot(fits, true_effect = 0.6)   # requires ggplot2

Under g1, occ is a confounder and must be included; under g2 it is a mediator and must be excluded; exper is optional in both. The two worlds license disjoint specifications, and the decomposition attributes the spread between them to the graph, not to specification noise.

What the package does

The DAG engine is written in base R: a parser for a subset of the dagitty syntax, d-separation by moralization, the generalized adjustment criterion (Shpitser, VanderWeele and Robins 2010; Perkovic et al. 2018) and the associated forbidden set, with latent nodes. mv_classify() labels each candidate control as required, forbidden or optional under a graph; the labels summarise the family of admissible subsets of the candidate pool, which mv_specs() returns in full. mv_run() fits each control subset once (engines: lm, negative binomial, fixest fixed effects) and records which subsets are admissible under each graph, so that adding a graph costs no estimation. mv_decompose() reports the Young and Holsteen (2017) metrics per graph and pooled, the within/between-DAG variance split and the structural share, under equal or user-supplied graph weights. mv_plot() draws the per-graph distributions against the pooled one.

A candidate graph under which no subset of the candidate pool is admissible is reported as not identified by adjustment, never dropped: mv_classify() returns the role "not identified" with attribute identified = FALSE; mv_run() and mv_decompose() keep the world with n_specs = 0, exclude it from the decomposition and say so when printed.

Tests

The test suite in tests/testthat/ covers back-door adjustment, M-bias, butterfly bias, mediators and their descendants, instruments, colliders and their descendants, a descendant of the exposure off the causal path, latent-variable restrictions, the eighteen models of Cinelli, Forney and Pearl (2024), a pair of graphs with identical role labels but different admissible families, the parser, and the reporting of worlds that are not identified. Randomised cross-checks on small DAGs compare adjustment_valid() with dagitty::isAdjustmentSet() (when dagitty is installed) and, without any additional package, with path-enumeration d-separation and with exact population OLS bias in random linear structural equation models. A plain-R battery without testthat is in inst/battery/test-core.R. From a checkout:

testthat::test_local()   # or R CMD check

Citation

citation("dagmv")

License

MIT; see LICENSE.

About

Causally disciplined multiverse analysis in R: candidate DAGs, valid-specification multiverses, within/between-DAG variance decomposition

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages