Suppose side-quest engagement is associated with in-game purchases. Does engagement cause purchases, or does guild membership influence both? A causal directed acyclic graph (DAG) represents these as different paths. D-separation checks which paths remain open after conditioning on selected variables, helping distinguish a causal route from a route through a common cause. section 7.2.4 section 4.1.2
Write U ⊥G V | Z to mean that node set Z d-separates U and V in graph G. Z is the conditioning set, also called the blockers. A path follows connected edges regardless of their arrow directions. U and V are d-separated only if every path between them is blocked; one open path makes them d-connected. The graphical claim is distinct from conditional independence in a probability distribution: it implies that independence if the DAG adequately represents the causal process. section 4.2.1 section 4.4.1
To check a path, inspect each middle node and the conditioning set:
- On a chain (i → m → j or i ← m ← j) or a common-cause fork (i ← m → j), conditioning on m blocks the path. section 4.2.1
- At a collider (i → m ← j), the path is blocked by default. Conditioning on m or one of its descendants can open it. Another node on that path may still block it. section 4.2.1 section 4.2.2
Figure 4.10 Colliders make d-connection tricky. Given a node m on a path, if m is not a collider, the path is d-connected by default and d-separated when you block on m. If m is a collider, the path is d-separated by default and d-connected when you block on m.
The diagram contrasts the default behavior of non-colliders and colliders when the middle node is conditioned on. section 4.2.1
A blocking set must cover every path
If U reaches V through U → I → V, U → J → V, and U → J → I → V, conditioning on {I, J} blocks all three. In a more complex graph, blocking one path can open another when the chosen blocker is a collider there. Recheck every path after adding a blocker; working from shorter paths toward longer unblocked ones can simplify the search. section 4.2.2
Listing 4.1 D-separation analysis of the DAG in figure 4.15
from networkx import is_d_separator #1
from pgmpy.base import DAG #2
dag = DAG([ #2
('I', 'U'), #2
('I', 'M'), #2
('M', 'U'), #2
('J', 'V'), #2
('J', 'M'), #2
('M', 'V') #2
]) #2
print(is_d_separator(dag, {"U"}, {"V"}, {"M"})) #3
print(is_d_separator(dag, {"U"}, {"V"}, {"M", "I", "J"})) #4
print(is_d_separator(dag, {"U"}, {"V"}, {"M", "I"})) #5
print(is_d_separator(dag, {"U"}, {"V"}, {"M", "J"})) #5
The Python listing constructs a DAG with pgmpy and uses NetworkX is_d_separator to compare candidate conditioning sets. section 4.2.3
D-separation is useful for both identification and model criticism. An adjustment set should block every backdoor path between treatment and outcome; an intervention represented by graph surgery instead removes incoming edges to its target. Conversely, if a proposed DAG d-separates variables that appear dependent in data, that evidence can refute the DAG. A statistical independence test can also err, so a failed or passed test is not an assumption-free verdict. section 11.3.1 section 4.3 section 4.4.1

