Identification Algorithms

Backdoor and frontdoor adjustment criteria delegate to CausalInference.jl gensearch. Backdoor listing follows the complete adjustment framework of van der Zander, Liśkiewicz, and Textor (2019); frontdoor find/min/list builds on the linear-time criterion of Wienöbst, van der Zander, and Liśkiewicz (2024). See References.

For singleton mediators, prefer find_frontdoor_mediators over list_frontdoor_adjustment_sets: the latter can materialise exponentially many sets on dense graphs.

Queries and certificates

CausalDynamics.TotalEffectQuery — Type
TotalEffectQuery(treatment, outcome)

Backdoor-identifiable total effect of treatment on outcome. Node labels may be Int indices or Symbol names (with a node_names map).

source
CausalDynamics.MediationQuery — Type
MediationQuery(treatment, outcome, mediators; moc=T[], effect_kind=:interventional)

Mediation decomposition (NDE / NIE / TE or path-specific) via mediators on directed paths. moc lists intermediate confounders (affected by treatment and affecting mediators/outcome). effect_kind is one of :natural, :interventional, :organic, :recanting_twin.

source
CausalDynamics.InterventionalPolicyQuery — Type
InterventionalPolicyQuery(treatment, outcome; shift=nothing)

Modified treatment policy / stochastic intervention contrast on treatment. shift is an application-defined policy descriptor (estimators interpret it).

source
CausalDynamics.TransportQuery — Type
TransportQuery(treatment, outcome, domain; source=nothing, target=nothing)

Transport a treatment effect across domains marked by domain covariates.

source
CausalDynamics.identify — Function
identify(g, query::TotalEffectQuery; node_names=nothing, missingness=nothing) -> IdentificationResult

Optional missingness::MissingnessSpec attaches a MissingnessCertificate on result.missingness (MAR/MNAR claims). Causal backdoor identifiable is unchanged by the missingness claim; check both fields before estimation.

source
identify(g, query::MediationQuery; node_names=nothing) -> IdentificationResult

Identification for mediation queries:

  • :natural — refuses when moc is nonempty or the graph has a recanting witness (treatment → Z → mediator and Z → outcome).
  • :interventional / :organic / :recanting_twin — allow intermediate confounders; return adjustment, mediators, and moc in the certificate.
source
identify(g, query::InterventionalPolicyQuery; node_names=nothing) -> IdentificationResult

Policy contrasts reduce to total-effect identification on the same (treatment, outcome) pair.

source
identify(unrolling::TemporalUnrolling, query::TemporalEffectQuery; missingness=nothing) -> IdentificationResult
source
identify(g, query::CausalQuery; kwargs...) -> IdentificationResult
source
identify(g, query::TransportQuery; node_names=nothing) -> IdentificationResult

Identify the total effect while unioning domain variables into the adjustment set. Strategy :transport_backdoor; assumptions include :transportability.

source
CausalDynamics.IdentificationResult — Type
IdentificationResult{T}

Machine-readable output of identify: adjustment sets, strategy, and assumptions.

Optional missingness holds a MissingnessCertificate when identify(...; missingness=MissingnessSpec(...)) was used. Causal identifiable and missingness identification are reported separately.

identification_status refines identifiable::Bool with the reason (:identified, :not_identified_by_procedure, :unsupported_model_class, :proved_nonidentifiable; see IDENTIFICATION_STATUSES). Optional semantic_fingerprint digests the declared meanings behind the graph.

source
CausalDynamics.MissingnessSpec — Type
MissingnessSpec

Structural claim about response indicators for incomplete observation.

Fields

  • response: outcome (or panel) symbols that may be unobserved (Y, Y1, …)
  • regime: :mcar, :mar, or :mnar
  • conditioning_set: covariates claimed for $P(R=1 \mid \cdot)$ under MAR
  • time_indexed: whether response indexes time steps of a timed process
  • indicators: optional explicit R node symbols (default: derived as R_<response>)

Does not estimate anything; use certify_missingness / identify(...; missingness=).

source
CausalDynamics.MissingnessCertificate — Type
MissingnessCertificate

Result of certify_missingness: whether the stated missingness regime identifies interventional / associational targets under incomplete observation.

Causal total-effect identification (IdentificationResult.identifiable) remains separate; estimators should consult both fields.

source
CausalDynamics.certify_missingness — Function
certify_missingness(spec::MissingnessSpec) -> MissingnessCertificate

Map a MissingnessSpec to an identification certificate:

  • :mcar — identified with empty mar_set
  • :mar — identified iff conditioning_set is nonempty
  • :mnar — unidentified without further modelling assumptions (Phase 3)

Optional graph / node_names are reserved for future edge checks against R nodes; they do not change the Phase 3 decision rule.

source
CausalDynamics.identification_report — Function
identification_report(g, treatment, outcome; node_names=nothing) -> Vector{NamedTuple}

Enumerate candidate adjustment sets and mark which satisfy the backdoor criterion. Returns a vector of named tuples (no DataFrames dependency).

source
CausalDynamics.temporal_adjustment_columns — Function
temporal_adjustment_columns(result, unrolling; skip, name_fn) -> Vector{Symbol}

Map result.temporal_nodes (pairs (variable, time)) to wide-table column symbols using panel_column_name by default.

Single-node supports map to their bare symbols; pointwise variables use name_fn(variable, time). Symbols in skip are omitted.

source
CausalDynamics.query_panel_columns — Function
query_panel_columns(unrolling, query::TemporalEffectQuery; name_fn) -> NamedTuple

Wide columns for treatment and outcome implied by a temporal query.

source
CausalDynamics.OutcomeKind — Type
OutcomeKind

Outcome family for a DAG node when planning targeted estimation.

Use OutcomeKind.count_outcome for nonnegative count nodes (planner sets family_outcome to :negbin or :poisson on EstimationPlan). Use OutcomeKind.binary for presence endpoints (family_outcome = :binomial).

source
CausalDynamics.NodeOutcomeSpec — Type
NodeOutcomeSpec(kind; presence_var=nothing, intensity_var=nothing)

Metadata for how a temporal node is observed in wide panels. For hurdle outcomes (OutcomeKind.hurdle), presence_var and intensity_var name the wide-table base symbols (time suffix added via panel_column_name).

source
CausalDynamics.plan_targeted_estimation — Function
plan_targeted_estimation(unrolling, query, column_names; ...)

Run temporal identification and return an EstimationPlan with engine choice (:discrete_lmtp, :two_part_discrete_lmtp, :lmtp_grid, :sequential_lmtp), treatment/outcome columns, and adjustment columns present in column_names.

discrete_treatment=true (default) selects :discrete_lmtp for same-time contrasts; set false for continuous shift LMTP.

outcome_specs maps DAG node symbols to NodeOutcomeSpec; hurdle nodes populate presence_col and intensity_col for two-part LMTP runners. OutcomeKind.binary sets family_outcome = :binomial; count_outcome sets :negbin by default or count_family = :poisson.

Pass data= (wide NamedTuple or table-like object) to attach empirical min_complete_n and estimability from identification_support on treatment, outcome, and adjustment columns.

source
CausalDynamics.identification_support — Function
identification_support(data, columns; min_n=10) -> NamedTuple

Empirical support for an adjustment / analysis column set on a row-aligned NamedTuple or DataFrame-like object (columns as vectors).

Returns min_complete_n (complete cases across columns) and estimability (:ok, :underpowered, or :empty when no rows remain).

source

Adjustment and instruments

CausalDynamics.backdoor_adjustment_set — Function
backdoor_adjustment_set(g, X, Y)

Find a valid backdoor adjustment set for estimating the causal effect of X on Y.

Delegates to CausalInference.find_min_backdoor_adjustment. Returns nothing if no valid set exists (false from CausalInference).

Arguments

  • g: Directed acyclic graph
  • X: Treatment node
  • Y: Outcome node

Returns

  • Set{Int} of adjustment nodes (possibly empty if no backdoors), or nothing
source
CausalDynamics.find_frontdoor_mediators — Function
find_frontdoor_mediators(g, X, Y)

Return singleton frontdoor adjustment sets between X and Y.

Each returned set has the form Set([m]) for a node m that is a valid frontdoor adjustment set on its own (typical textbook mediator between X and Y). Validates each candidate with frontdoor_adjustment_set rather than enumerating all frontdoor sets.

source
CausalDynamics.find_path_mediators — Function
find_path_mediators(g, treatment, outcome) -> Set{Int}

Structural mediator candidates between treatment and outcome: nodes that lie on at least one proper directed path treatment → ⋯ → outcome (excluding the endpoints).

On a DAG this is

intersect(get_descendants(g, treatment), get_ancestors(g, outcome))

This is not the frontdoor criterion. For single-node sets that satisfy the frontdoor criterion, use find_frontdoor_mediators.

Arguments

  • g: Directed acyclic graph
  • treatment: Treatment node index
  • outcome: Outcome node index

Returns

  • Set{Int} of mediator candidate indices

Examples

using CausalDynamics, Graphs

# Nodes: A=1, M1=2, M2=3, M3=4, Y=5, C=6, D=7
# A → M1 → M2 → Y; A → M3 → Y; C → A; C → Y; A → D
g = DiGraph(7)
add_edge!(g, 1, 2); add_edge!(g, 2, 3); add_edge!(g, 3, 5)
add_edge!(g, 1, 4); add_edge!(g, 4, 5)
add_edge!(g, 6, 1); add_edge!(g, 6, 5)
add_edge!(g, 1, 7)

find_path_mediators(g, 1, 5)  # Set([2, 3, 4]) == {M1, M2, M3}
source
find_path_mediators(g, treatment, outcome; node_names) -> Set

As find_path_mediators with Int indices, but treatment / outcome may be Symbols when node_names maps indices to names (same conventions as identify).

Returns a Set{Symbol} when node_names is provided, otherwise Set{Int}.

source
CausalDynamics.find_minimal_mediator_sets — Function
find_minimal_mediator_sets(g, treatment, outcome; max_candidates=20) -> MinimalMediatorSets{Int}

Inclusion-minimal sets of mediators that intercept every directed path treatment → ⋯ → outcome.

A set $S$ intercepts all directed paths when intercepts_all_directed_paths (g, treatment, outcome, S) is true. Equivalently, $S$ is a hitting set for the intermediate nodes of every directed path.

Returns a MinimalMediatorSets with:

  • sets: all inclusion-minimal cuts, sorted by (length, sorted members)
  • status: :ok, :no_path, :uncuttable_direct_edge, or :uncuttable

Uses directed reachability (BFS with forbidden nodes), not full path enumeration. Candidate nodes are find_path_mediators. Enumeration of subsets is exponential in the number of candidates; max_candidates caps that search.

This is not the frontdoor criterion — see find_frontdoor_mediators. Nor does it choose mediators for MediationQuery; pass a chosen set explicitly after inspecting .sets.

Examples

using CausalDynamics, Graphs

# Sequential: A → M1 → M2 → Y
g = DiGraph(4)
add_edge!(g, 1, 2); add_edge!(g, 2, 3); add_edge!(g, 3, 4)
r = find_minimal_mediator_sets(g, 1, 4)
r.status  # :ok
r.sets    # [Set([2]), Set([3])]

# Parallel: A → M1 → Y and A → M2 → Y
g = DiGraph(4)
add_edge!(g, 1, 2); add_edge!(g, 2, 4)
add_edge!(g, 1, 3); add_edge!(g, 3, 4)
find_minimal_mediator_sets(g, 1, 4).sets  # [Set([2, 3])]

# Mixed: A → M1 → M3 → Y and A → M2 → M3 → Y
g = DiGraph(5)
add_edge!(g, 1, 2); add_edge!(g, 2, 4); add_edge!(g, 4, 5)
add_edge!(g, 1, 3); add_edge!(g, 3, 4)
find_minimal_mediator_sets(g, 1, 5).sets  # [Set([4]), Set([2, 3])]
source
find_minimal_mediator_sets(g, treatment, outcome; node_names, max_candidates=20)

As find_minimal_mediator_sets with Int indices. With node_names, returns MinimalMediatorSets{Symbol}.

source
CausalDynamics.MinimalMediatorSets — Type
MinimalMediatorSets{T}

Result of find_minimal_mediator_sets.

Fields

  • sets: Inclusion-minimal mediator sets, sorted by (length, sorted members)
  • status:
    • :ok — sets holds the cuts (possibly empty only in degenerate cases)
    • :no_path — no directed path from treatment to outcome
    • :uncuttable_direct_edge — a direct treatment→outcome edge cannot be cut by mediators
    • :uncuttable — a residual directed path avoids every path-mediator candidate

Iterates over sets (so collect(result) and Set(result) still work).

source
CausalDynamics.find_instruments — Function
find_instruments(g, X, Y)

Find instrumental variables for estimating the causal effect of X on Y.

An instrumental variable Z must satisfy:

  1. Z has a causal effect on X
  2. Z affects Y only through X (exclusion restriction)
  3. Z is independent of confounders of X and Y (independence)

Arguments

  • g: Directed acyclic graph
  • X: Treatment node
  • Y: Outcome node

Returns

  • Vector of potential instrumental variables

Examples

using CausalDynamics, Graphs

# IV example
g = DiGraph(4)
add_edge!(g, 1, 2)  # Z → X
add_edge!(g, 2, 3)  # X → Y
add_edge!(g, 4, 2)  # U → X
add_edge!(g, 4, 3)  # U → Y

# Z is a valid instrument
instruments = find_instruments(g, 2, 3)  # [1]

References

  • Angrist, J. D., & Pischke, J. S. (2009). Mostly Harmless Econometrics
source
CausalDynamics.is_valid_instrument — Function
is_valid_instrument(g, Z, X, Y)

Check if Z is a valid instrumental variable for X → Y.

An instrument Z must satisfy three conditions:

  1. Relevance: Z has a causal effect on X (directed path Z → X)
  2. Exclusion restriction: Z affects Y only through X (all paths Z → Y go through X)
  3. Independence: Z is independent of confounders (no backdoor paths Z → Y)

Arguments

  • g::AbstractGraph: Directed acyclic graph
  • Z::Int: Potential instrument node
  • X::Int: Treatment node
  • Y::Int: Outcome node

Returns

  • Bool: true if Z is a valid instrument, false otherwise

Examples

using CausalDynamics, Graphs

# Valid instrument
g = DiGraph(4)
add_edge!(g, 1, 2)  # Z → X
add_edge!(g, 2, 3)  # X → Y
add_edge!(g, 4, 2)  # U → X
add_edge!(g, 4, 3)  # U → Y

is_valid_instrument(g, 1, 2, 3)  # true

# Invalid: Z has direct path to Y
g2 = DiGraph(3)
add_edge!(g2, 1, 2)  # Z → X
add_edge!(g2, 1, 3)  # Z → Y (violates exclusion)
add_edge!(g2, 2, 3)  # X → Y

is_valid_instrument(g2, 1, 2, 3)  # false

Notes

  • The independence condition (3) is checked conservatively: if any backdoor paths exist, returns false
  • In practice, independence may hold even with backdoor paths if they are blocked

References

  • Angrist, J. D., & Pischke, J. S. (2009). Mostly Harmless Econometrics, Chapter 4
source

Boosted control functions

The reference implementation of boosted control functions fits the linear SIMDG/control-function construction described by Gnecco et al. (2026). It is intended for prediction under shifts induced by exogenous variables, not as a replacement for graph-based causal effect identification.

CausalDynamics.BoostedControlFunctionFit — Type
BoostedControlFunctionFit

Fitted linear boosted control-function predictor. M is the estimated reduced-form coefficient in X = M * Z + V; R spans the left null space of M; and f_coefficients and δ_coefficients define the fitted BCF.

This is a dependency-free linear reference implementation of the estimator in Gnecco et al. (2026). Nonlinear learners can be layered on the same three stages, but are deliberately not hidden behind this small API.

source
CausalDynamics.boosted_control_function — Function
boosted_control_function(X, Y, Z; ridge=0.0)

Fit the linear boosted control function (BCF) from observations of endogenous covariates X, outcome Y, and exogenous shift variables Z.

The implementation follows Algorithm 1 of Gnecco, Peters, Engelke and Pfister (2026): estimate M, form the control variable V = X - M Z, fit an additive regression of Y on (X, V), then project its control-function contribution onto the invariant coordinates R'X. ridge provides optional Tikhonov regularisation for ill-conditioned design matrices.

The returned object predicts the BCF target, which is a robust predictive function under the paper's SIMDG assumptions; it is not automatically a causal effect estimator.

source
CausalDynamics.predict — Function
predict(fit::BoostedControlFunctionFit, X)

Predict outcomes with a fitted boosted control function. Call CausalDynamics.predict(fit, X) (predict is not exported, to avoid clashing with StatsAPI / GLM). New rows of X must have the same covariate ordering and number of columns as the training data.

source

Column resolvers

Map graph node labels to data columns after identification.