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.CausalQuery — Type
CausalQuerySupertype for queries posed against a causal graph or temporal unrolling.
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).
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.
CausalDynamics.TemporalEffectQuery — Type
TemporalEffectQuery(treatment, outcome, t_treat, t_outcome)Total effect of treatment at time t_treat on outcome at t_outcome after unrolling a TemporalDAGSpec.
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).
CausalDynamics.TransportQuery — Type
TransportQuery(treatment, outcome, domain; source=nothing, target=nothing)Transport a treatment effect across domains marked by domain covariates.
CausalDynamics.identify — Function
identify(g, query::TotalEffectQuery; node_names=nothing, missingness=nothing) -> IdentificationResultOptional 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.
identify(g, query::MediationQuery; node_names=nothing) -> IdentificationResultIdentification for mediation queries:
:natural— refuses whenmocis 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.
identify(g, query::InterventionalPolicyQuery; node_names=nothing) -> IdentificationResultPolicy contrasts reduce to total-effect identification on the same (treatment, outcome) pair.
identify(unrolling::TemporalUnrolling, query::TemporalEffectQuery; missingness=nothing) -> IdentificationResultidentify(g, query::CausalQuery; kwargs...) -> IdentificationResultidentify(g, query::TransportQuery; node_names=nothing) -> IdentificationResultIdentify the total effect while unioning domain variables into the adjustment set. Strategy :transport_backdoor; assumptions include :transportability.
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.
CausalDynamics.IdentificationError — Type
IdentificationError <: ExceptionThrown when a mediation (or other) query is not identified under the stated effect kind and graph structure.
CausalDynamics.MissingnessSpec — Type
MissingnessSpecStructural claim about response indicators for incomplete observation.
Fields
response: outcome (or panel) symbols that may be unobserved (Y,Y1, …)regime::mcar,:mar, or:mnarconditioning_set: covariates claimed for $P(R=1 \mid \cdot)$ under MARtime_indexed: whetherresponseindexes time steps of a timed processindicators: optional explicitRnode symbols (default: derived asR_<response>)
Does not estimate anything; use certify_missingness / identify(...; missingness=).
CausalDynamics.MissingnessCertificate — Type
MissingnessCertificateResult 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.
CausalDynamics.certify_missingness — Function
certify_missingness(spec::MissingnessSpec) -> MissingnessCertificateMap a MissingnessSpec to an identification certificate:
:mcar— identified with emptymar_set:mar— identified iffconditioning_setis 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.
CausalDynamics.graph_fingerprint — Function
graph_fingerprint(g) -> UInt64Stable digest of directed edges for reproducibility certificates.
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).
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.
CausalDynamics.adjustment_columns — Function
adjustment_columns(unrolling, query; kwargs...) -> Vector{Symbol}Identify a TemporalEffectQuery on unrolling and return wide baseline column names for estimation (convenience wrapper around temporal_adjustment_columns).
CausalDynamics.query_panel_columns — Function
query_panel_columns(unrolling, query::TemporalEffectQuery; name_fn) -> NamedTupleWide columns for treatment and outcome implied by a temporal query.
CausalDynamics.OutcomeKind — Type
OutcomeKindOutcome 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).
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).
CausalDynamics.EstimationPlan — Type
EstimationPlanPlanner output linking temporal identification to a CausalTargeted engine and wide-panel column names.
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.
CausalDynamics.plan_session_estimation — Method
plan_session_estimation(unrolling, query, session; kwargs...) -> EstimationPlanLike plan_targeted_estimation for a single session slice from long capture data. When query.t_outcome != session, runs check_time_resolution and warns if resolution mismatches.
Pass data= as the session slice (see session_slice), not the full long panel.
CausalDynamics.identification_support — Function
identification_support(data, columns; min_n=10) -> NamedTupleEmpirical 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).
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 graphX: Treatment nodeY: Outcome node
Returns
Set{Int}of adjustment nodes (possibly empty if no backdoors), ornothing
CausalDynamics.is_backdoor_adjustable — Function
is_backdoor_adjustable(g, X, Y)Return true if a backdoor adjustment set exists (including the empty set when there are no backdoor paths).
CausalDynamics.frontdoor_adjustment_set — Function
frontdoor_adjustment_set(g, X, Y, M) -> BoolReturn true when M is a valid frontdoor adjustment set for the effect of X on Y.
Delegates to CausalInference.find_frontdoor_adjustment with $I = R = M$ (exact-set check). Uses the general frontdoor search of Wienöbst et al. (2024) via CausalInference gensearch (see also van der Zander et al., 2019 for backdoor listing).
CausalDynamics.find_frontdoor_adjustment_set — Function
find_frontdoor_adjustment_set(g, X, Y)Find a frontdoor adjustment set for the effect of X on Y.
Delegates to CausalInference.find_frontdoor_adjustment. Returns nothing when no valid set exists (false from CausalInference).
CausalDynamics.find_min_frontdoor_adjustment_set — Function
find_min_frontdoor_adjustment_set(g, X, Y)Find an inclusion-minimal frontdoor adjustment set, or nothing if none exists.
Delegates to CausalInference.find_min_frontdoor_adjustment.
CausalDynamics.list_frontdoor_adjustment_sets — Function
list_frontdoor_adjustment_sets(g, X, Y)List all frontdoor adjustment sets for the effect of X on Y.
Delegates to CausalInference.list_frontdoor_adjustment. This can materialise exponentially many sets on dense graphs; prefer find_frontdoor_adjustment_set, find_min_frontdoor_adjustment_set, or find_frontdoor_mediators when a single set or singleton mediators suffice.
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.
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 graphtreatment: Treatment node indexoutcome: 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}find_path_mediators(g, treatment, outcome; node_names) -> SetAs 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}.
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])]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}.
CausalDynamics.MinimalMediatorSets — Type
MinimalMediatorSets{T}Result of find_minimal_mediator_sets.
Fields
sets: Inclusion-minimal mediator sets, sorted by(length, sorted members)status::ok—setsholds 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).
CausalDynamics.intercepts_all_directed_paths — Function
intercepts_all_directed_paths(g, treatment, outcome, S) -> BoolReturn true if mediator set S intercepts every directed path from treatment to outcome (no directed route avoids every node in S). Endpoints are never forbidden. Same criterion used by find_minimal_mediator_sets.
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:
- Z has a causal effect on X
- Z affects Y only through X (exclusion restriction)
- Z is independent of confounders of X and Y (independence)
Arguments
g: Directed acyclic graphX: Treatment nodeY: 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
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:
- Relevance: Z has a causal effect on X (directed path Z → X)
- Exclusion restriction: Z affects Y only through X (all paths Z → Y go through X)
- Independence: Z is independent of confounders (no backdoor paths Z → Y)
Arguments
g::AbstractGraph: Directed acyclic graphZ::Int: Potential instrument nodeX::Int: Treatment nodeY::Int: Outcome node
Returns
Bool:trueif Z is a valid instrument,falseotherwise
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) # falseNotes
- 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
CausalDynamics.find_all_adjustment_sets — Function
find_all_adjustment_sets(g, X, Y)List valid backdoor adjustment sets via CausalInference.list_backdoor_adjustment.
CausalDynamics.is_valid_adjustment_set — Function
is_valid_adjustment_set(g, X, Y, Z)Check if Z is a valid backdoor adjustment set via CausalInference.
CausalDynamics.minimal_adjustment_set — Function
minimal_adjustment_set(g, X, Y)Minimal backdoor adjustment set (same as backdoor_adjustment_set).
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
BoostedControlFunctionFitFitted 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.
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.
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.
Column resolvers
Map graph node labels to data columns after identification.
CausalDynamics.ColumnResolver — Type
ColumnResolverTrait-like API for resolving identification nodes to data columns.
CausalDynamics.IdentityColumnResolver — Type
IdentityColumnResolverPass node labels through unchanged (symbols or indices).
CausalDynamics.DictColumnResolver — Type
DictColumnResolver(mapping)Resolve via explicit node => column mapping.
CausalDynamics.resolve_columns — Function
resolve_columns(resolver, nodes, available) -> VectorReturn columns present in available that correspond to nodes.
CausalDynamics.resolve_identification_columns — Function
resolve_identification_columns(result, resolver, column_names) -> IdentificationResult