Apply do-calculus to transform interventional queries into observational queries
Recognise when do-calculus applies vs template-based methods
Use do-calculus to determine identifiability
9.2 Introduction
The do-calculus provides rules for computing interventional distributions from observational distributions (Pearl 2009). These rules allow us to answer: βCan we express \(P^{do(X=x)}(Y)\) in terms of \(P(Y, X, Z)\)?β While template-based methods (backdoor, frontdoor, IV) are often more intuitive, do-calculus provides the theoretical foundation and handles cases where templates donβt apply.
9.3 The Three Rules of Do-Calculus
The do-calculus consists of three rules that allow us to manipulate interventional distributions algebraically. These rules are based on d-separation in modified graphs (see Graph Theory and Causal Patterns for d-separation).
9.3.1 Rule 1: Insertion/Deletion of Observations
Rule 1 (Insertion/Deletion of Observations): If \(Y\) and \(Z\) are d-separated by \(X\) in \(G_{\overline{X}}\) (graph with edges into \(X\) removed), then: \[
P^{do(X=x)}(Y \mid Z) = P^{do(X=x)}(Y)
\]
Interpretation: If \(Y\) and \(Z\) are conditionally independent given the intervention on \(X\) (in the modified graph), then we can insert or delete the observation of \(Z\) without changing the interventional distribution.
When to use: When we want to simplify an interventional query by removing unnecessary conditioning.
9.3.2 Implementation: Rule 1 Example
We can check Rule 1 by creating the modified graph \(G_{\overline{X}}\) (edges into \(X\) removed) and checking d-separation:
# Find project root and include ensure_packages.jlproject_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# Example graph: Z β X β Y, Z β Y# We want to check: P^{do(X=x)}(Y | Z) = P^{do(X=x)}(Y)?# Rule 1 applies if Y and Z are d-separated by X in G_overline{X}# Original graphg =SimpleDiGraph(3)add_edge!(g, 3, 1) # Z β Xadd_edge!(g, 1, 2) # X β Yadd_edge!(g, 3, 2) # Z β Y# G_overline{X}: Remove edges INTO Xg_overline_X =copy(g)rem_edge!(g_overline_X, 3, 1) # Remove Z β Xprintln("Original graph: Z β X β Y, Z β Y")println("G_overline{X}: Remove edges into X")println(" Y β«« Z | X in G_overline{X: ", CausalDynamics.d_separated(g_overline_X, 2, 3, [1]))if CausalDynamics.d_separated(g_overline_X, 2, 3, [1])println(" β Rule 1 applies: P^{do(X=x)}(Y | Z) = P^{do(X=x)}(Y)")elseprintln(" β Rule 1 does not apply")end
Original graph: Z β X β Y, Z β Y
G_overline{X}: Remove edges into X
Y β«« Z | X in G_overline{X: false
β Rule 1 does not apply
9.3.3 Rule 2: Action/Observation Exchange
Rule 2 (Action/Observation Exchange): If \(Y\) and \(Z\) are d-separated by \(X\) in \(G_{\underline{X}}\) (graph with edges out of \(X\) removed), then: \[
P^{do(X=x)}(Y \mid Z) = P(Y \mid Z, X=x)
\]
Interpretation: If \(Y\) and \(Z\) are conditionally independent given \(X\) in the graph with edges out of \(X\) removed, then we can exchange the intervention \(do(X=x)\) with the observation \(X=x\).
When to use: When we want to convert an interventional query into an observational query (this is the key to identification).
9.3.4 Implementation: Rule 2 Example
Rule 2 is the key to identificationβit converts interventions to observations. We check d-separation in \(G_{\underline{X}}\) (edges out of \(X\) removed):
# Find project root and include ensure_packages.jlproject_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 CausalDynamics Graphs GraphMakie CairoMakie DAGMakie# Example graph: Z β X β Y# We want to check: P^{do(X=x)}(Y | Z) = P(Y | Z, X=x)?# Rule 2 applies if Y and Z are d-separated by X in G_underline{X}# Original graphg =SimpleDiGraph(3)add_edge!(g, 3, 1) # Z β Xadd_edge!(g, 1, 2) # X β Y# G_underline{X}: Remove edges OUT OF Xg_underline_X =copy(g)rem_edge!(g_underline_X, 1, 2) # Remove X β Yprintln("Original graph: Z β X β Y")println("G_underline{X}: Remove edges out of X")println(" Y β«« Z | X in G_underline{X: ", CausalDynamics.d_separated(g_underline_X, 2, 3, [1]))if CausalDynamics.d_separated(g_underline_X, 2, 3, [1])println(" β Rule 2 applies: P^{do(X=x)}(Y | Z) = P(Y | Z, X=x)")println(" This converts the interventional query to an observational query!")elseprintln(" β Rule 2 does not apply")end# Visualise the modified graphlet# Original graph: Z β X β Y fig1, ax1, p1 =dagplot(g; figure_size = (800, 400), layout_mode =:acyclic, nlabels = ["X", "Y", "Z"] )# Modified graph G_underline{X}: Remove X β Y fig2, ax2, p2 =dagplot(g_underline_X; figure_size = (800, 400), layout_mode =:acyclic, node_color =:lightgreen, nlabels = ["X", "Y", "Z"] ) fig1 fig2end
Original graph: Z β X β Y
G_underline{X}: Remove edges out of X
Y β«« Z | X in G_underline{X: true
β Rule 2 applies: P^{do(X=x)}(Y | Z) = P(Y | Z, X=x)
This converts the interventional query to an observational query!
Do-calculus Rule 2: Action/observation exchange (key to identification)
9.3.5 Rule 3: Insertion/Deletion of Actions
Rule 3 (Insertion/Deletion of Actions): If \(Y\) and \(Z\) are d-separated by \(X\) in \(G_{\overline{X}\underline{Z}}\) (graph with edges into \(X\) and out of \(Z\) removed), then: \[
P^{do(X=x, Z=z)}(Y) = P^{do(X=x)}(Y)
\]
Interpretation: If \(Y\) and \(Z\) are conditionally independent given the intervention on \(X\) (in the modified graph), then we can insert or delete the intervention on \(Z\) without changing the interventional distribution.
When to use: When we want to simplify an interventional query by removing unnecessary interventions.
9.3.6 Implementation: Rule 3 Example
Rule 3 allows us to remove unnecessary interventions. We check d-separation in \(G_{\overline{X}\underline{Z}}\) (edges into \(X\) and out of \(Z\) removed):
# Find project root and include ensure_packages.jlproject_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 CausalDynamics Graphs# Example graph: X β Y β Z, X β Z# We want to check: P^{do(X=x, Z=z)}(Y) = P^{do(X=x)}(Y)?# Rule 3 applies if Y and Z are d-separated by X in G_overline{X,underline{Z}}# Original graphg =SimpleDiGraph(3)add_edge!(g, 1, 2) # X β Yadd_edge!(g, 3, 2) # Z β Yadd_edge!(g, 1, 3) # X β Z# G_overline{X,underline{Z}}: Remove edges INTO X and OUT OF Zg_overline_X_underline_Z =copy(g)# No edges into X to remove (X has no parents)rem_edge!(g_overline_X_underline_Z, 3, 2) # Remove Z β Y (edge out of Z)println("Original graph: X β Y β Z, X β Z")println("G_overline{X,underline{Z}}: Remove edges into X and out of Z")println(" Y β«« Z | X in G_overline{X,underline{Z}: ", CausalDynamics.d_separated(g_overline_X_underline_Z, 2, 3, [1]))if CausalDynamics.d_separated(g_overline_X_underline_Z, 2, 3, [1])println(" β Rule 3 applies: P^{do(X=x, Z=z)}(Y) = P^{do(X=x)}(Y)")println(" We can remove the intervention on Z without changing the result")elseprintln(" β Rule 3 does not apply")end
Original graph: X β Y β Z, X β Z
G_overline{X,underline{Z}}: Remove edges into X and out of Z
Y β«« Z | X in G_overline{X,underline{Z}: true
β Rule 3 applies: P^{do(X=x, Z=z)}(Y) = P^{do(X=x)}(Y)
We can remove the intervention on Z without changing the result
9.4 Systematic Application
These rules allow us to systematically transform interventional queries into observational queries when identification is possible. The process:
Start with interventional query: \(P^{do(X=x)}(Y)\)
Apply rules: Use Rules 1-3 to transform the query
Check if observational: If we can express it as \(P(Y \mid \ldots)\), the effect is identified
If not identifiable: If do-calculus cannot find an expression, the effect is not identifiable
9.5 When Do-Calculus Applies vs Template-Based Methods
Template-based methods (backdoor, frontdoor, IV) are: - More intuitive - Computationally efficient - Easy to apply when patterns match
Do-calculus is: - Theoretically complete - Handles cases where templates donβt apply - Provides the foundation for template-based methods
Best practice: Try template-based methods first (theyβre easier), then use do-calculus for cases where templates donβt apply or to verify template results.
9.6 Example: Applying Do-Calculus
Consider a graph where we want to identify \(P^{do(X=x)}(Y)\):
Check template methods: Does backdoor/frontdoor/IV apply?
If not, try do-calculus: Apply Rules 1-3 systematically
Verify result: Check if we can express it in terms of observable distributions
9.6.1 Implementation: Systematic Application Example
Hereβs a complete example showing how to systematically apply do-calculus rules:
# Find project root and include ensure_packages.jlproject_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 CausalDynamics Graphs# Example: Graph Z β X β Y, Z β Y# Query: Can we identify P^{do(X=x)}(Y)?g =SimpleDiGraph(3)add_edge!(g, 3, 1) # Z β Xadd_edge!(g, 1, 2) # X β Yadd_edge!(g, 3, 2) # Z β Yprintln("Graph: Z β X β Y, Z β Y")println("Query: P^{do(X=x)}(Y)")println("\nStep 1: Check template methods (backdoor)")adj_set =backdoor_adjustment_set(g, 1, 2) # X β Yif !isempty(adj_set)println(" Backdoor adjustment set: ", adj_set)println(" β Can use backdoor criterion (easier than do-calculus)")elseprintln(" β Backdoor criterion doesn't apply")println(" β Try do-calculus")# Try Rule 2: P^{do(X=x)}(Y) = P(Y | X=x) if Y β«« Z | X in G_underline{X} g_underline_X =copy(g)rem_edge!(g_underline_X, 1, 2) # Remove X β Yif CausalDynamics.d_separated(g_underline_X, 2, 3, [1])println("\nStep 2: Apply Rule 2")println(" Y β«« Z | X in G_underline{X: true")println(" β P^{do(X=x)}(Y) = P(Y | X=x)")println(" β Identified!")endend
Graph: Z β X β Y, Z β Y
Query: P^{do(X=x)}(Y)
Step 1: Check template methods (backdoor)
Backdoor adjustment set: Set([3])
β Can use backdoor criterion (easier than do-calculus)
9.7 World Context
This chapter addresses Doing in the Structural worldβhow can we compute interventional distributions? Do-calculus provides the symbolic rules for transforming interventions into observations, complementing the template-based methods in Identification: When Can We Learn from Data?.
9.8 Key Takeaways
Three rules: Insertion/deletion of observations, action/observation exchange, insertion/deletion of actions
Systematic transformation: Rules allow us to transform interventional queries into observational queries
Theoretical completeness: Do-calculus provides the foundation for all identification methods
Complement to templates: Use template methods when possible, do-calculus when needed
Based on d-separation: Rules rely on conditional independence in modified graphs
9.9 Further Reading
Pearl (2009): Causality, Chapter 3 β Complete treatment of do-calculus
Bareinboim and Pearl (2016): βCausal inference and the data-fusion problemβ
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.