The do-calculus is Pearlβs symbolic calculus for rewriting interventional distributions in terms of observational ones (Pearl 2009). It answers: can \(P(Y \mid do(X=x))\) be expressed using \(P(\cdot)\) on the observed variables? Template criteria (backdoor, frontdoor, instruments) in Chapter 5 are the usual first tools; do-calculus is the complete foundation those templates sit on, and the fallback when no template matches.
CausalDynamics does not yet implement a full symbolic do-calculus solver (is_identifiable / identify_formula remain stubs). What we can do here is check the graphical side conditions of each rule with d_separated on surgically modified graphs, and fall back to identify / backdoor_adjustment_set for everyday queries.
9.2 Modified graphs
Each rule refers to a mutilated DAG:
Notation
Surgery
Display helper
\(G_{\overline{X}}\)
remove incoming edges to \(X\)
DAGMakie do_surgery (plotting only)
\(G_{\underline{X}}\)
remove outgoing edges from \(X\)
manual rem_edge! on a copy
\(G_{\overline{X}\underline{Z}}\)
both of the above for the named nodes
combine the two
Executable SCM interventions (apply_intervention) also remove incoming edges to the intervened node; that is the same mutilation as \(G_{\overline{X}}\), but for simulation rather than display (Chapter 4; Tables 4 and 8 in the Concept Reference).
9.3 The three rules
9.3.1 Rule 1: Insertion / deletion of observations
If \((Y \perp Z \mid X)_{G_{\overline{X}}}\), then
\[
P(Y \mid do(X=x), Z) = P(Y \mid do(X=x)).
\]
Conditioning on \(Z\) is redundant under the intervention when \(Z\) carries no information about \(Y\) in the graph with parents of \(X\) cut.
project_root =let current =pwd()while !isfile(joinpath(current, "Project.toml")) && !isfile(joinpath(current, "_quarto.yml")) parent =dirname(current) parent == current &&break current = parentend currentendinclude(joinpath(project_root, "scripts", "ensure_packages.jl"))@auto_using DAGMakie CairoMakie CausalDynamics Graphs# Z β X β Y, Z β Y (nodes: 1=X, 2=Y, 3=Z)g =SimpleDiGraph(3)add_edge!(g, 3, 1)add_edge!(g, 1, 2)add_edge!(g, 3, 2)g_bar_X =do_surgery(g, 1) # G_overline{X}: drop edges into Xrule1 =d_separated(g_bar_X, 2, 3, [1])println("Graph: Z β X β Y, Z β Y")println("Y β«« Z | X in G_overline{X}? ", rule1)println(rule1 ? "Rule 1 applies.":"Rule 1 does not apply (here Z β Y stays open).")
Graph: Z β X β Y, Z β Y
Y β«« Z | X in G_overline{X}? false
Rule 1 does not apply (here Z β Y stays open).
Left: original confounding DAG. Right: \(G_{\overline{X}}\) after do_surgery (edge \(Z \rightarrow X\) removed).
9.3.2 Rule 2: Action / observation exchange
If \((Y \perp Z \mid X)_{G_{\underline{X}}}\), then
\[
P(Y \mid do(X=x), Z) = P(Y \mid Z, X=x).
\]
This is the usual bridge from intervention to observation (given covariates \(Z\)).
# Chain Z β X β Y (no direct Z β Y)g2 =SimpleDiGraph(3)add_edge!(g2, 3, 1)add_edge!(g2, 1, 2)g_under_X =copy(g2)for c incollect(outneighbors(g_under_X, 1))rem_edge!(g_under_X, 1, c)endrule2 =d_separated(g_under_X, 2, 3, [1])println("Graph: Z β X β Y")println("Y β«« Z | X in G_underline{X}? ", rule2)println(rule2 ? "Rule 2 applies: P(Y | do(X), Z) = P(Y | Z, X).":"Rule 2 does not apply.")
Graph: Z β X β Y
Y β«« Z | X in G_underline{X}? true
Rule 2 applies: P(Y | do(X), Z) = P(Y | Z, X).
Left: chain \(Z \rightarrow X \rightarrow Y\). Right: \(G_{\underline{X}}\) with outgoing edges from \(X\) removed.
9.3.3 Rule 3: Insertion / deletion of actions
If \((Y \perp Z \mid X)_{G_{\overline{X}\underline{Z}}}\), then
\[
P(Y \mid do(X=x, Z=z)) = P(Y \mid do(X=x)).
\]
An extra intervention on \(Z\) can be dropped when it does not open a path to \(Y\) under that double surgery.
# X β Y β Z, X β Zg3 =SimpleDiGraph(3)add_edge!(g3, 1, 2)add_edge!(g3, 3, 2)add_edge!(g3, 1, 3)g_bar_X_under_Z =do_surgery(g3, 1)for c incollect(outneighbors(g_bar_X_under_Z, 3))rem_edge!(g_bar_X_under_Z, 3, c)endrule3 =d_separated(g_bar_X_under_Z, 2, 3, [1])println("Graph: X β Y β Z, X β Z")println("Y β«« Z | X in G_overline{X} underline{Z}? ", rule3)println(rule3 ? "Rule 3 applies: do(Z) can be deleted beside do(X).":"Rule 3 does not apply.")
Graph: X β Y β Z, X β Z
Y β«« Z | X in G_overline{X} underline{Z}? true
Rule 3 applies: do(Z) can be deleted beside do(X).
9.4 Templates first, calculus when needed
Practice: try identify / backdoor / frontdoor / IV (Chapter 5). If a template succeeds, you already have an observational formula. Use the rule checks above to understand why a rewrite is valid, or when templates fail and a longer do-calculus derivation would be required (still done by hand until a symbolic solver lands in CausalDynamics).
g_q =SimpleDiGraph(3)add_edge!(g_q, 3, 1)add_edge!(g_q, 1, 2)add_edge!(g_q, 3, 2)println("Query: total effect of X on Y in Z β X β Y, Z β Y")id =identify(g_q, TotalEffectQuery(1, 2); node_names = [:X, :Y, :Z])println("identify β strategy=$(id.strategy), adjustment=$(id.adjustment)")println("Backdoor set: ", backdoor_adjustment_set(g_q, 1, 2))
Query: total effect of X on Y in Z β X β Y, Z β Y
identify β strategy=backdoor, adjustment=[:Z]
Backdoor set: Set([3])
9.5 Stratum context
This chapter is doing in the Structural stratum: algebraic rules that turn interventions into observations when the graph permits. Estimation of the resulting functionals lives in the Observable part.
9.6 Summary
Do-calculus has three rules, each conditioned on d-separation in a surgically modified DAG. Template identification is the everyday path; d_separated on \(G_{\overline{X}}\) / \(G_{\underline{X}}\) makes the graphical hypotheses of the rules checkable in code. Full symbolic rewriting is not yet in CausalDynamics; use identify for certificates that templates provide.
Bareinboim, Elias, and Judea Pearl. 2016. βCausal Inference and the Data-Fusion Problem.βProceedings of the National Academy of Sciences 113 (27): 7345β52.
Pearl, Judea. 2009. Causality: Models, Reasoning, and Inference. 2nd ed. Cambridge University Press.