Type at least 2 letters. Enter opens the first result, Esc closes.

    Causal Inference · Lesson 10 · Causal ML, counterfactuals and discovery

    Causal discovery: learning the graph from data

    Advanced 90 min 5 interactive figures 6 videos Colab notebook

    Before this lesson: lesson 03 (d-separation, v-structures, the Markov property and faithfulness) and lesson 04 (the do-operator). From probability: conditional independence (probability 04), partial correlation in the multivariate Gaussian (probability 05) and hypothesis tests (probability 11). Time: about 90 minutes, plus the notebook.


    Why this matters #

    • Science. Biologists learn gene regulatory and protein signalling networks from measurements; the classic study of Sachs et al. (2005) recovered most known links of a signalling network from single-cell data with interventions. Climate and Earth scientists use the same ideas on time series (for example the PCMCI method of Runge et al. 2019).
    • Engineering and operations. Root-cause analysis in large software systems learns which service's latency drives which other one; discovery on monitoring data suggests the graph that an engineer then checks.
    • Machine learning. The adjustment rules of lessons 04–08 need a graph. When nobody can draw it with confidence, discovery gives candidate graphs, and the equivalence class shows which conclusions do not depend on the unknown directions. Discovery ideas also appear in feature selection (Markov blankets) and in invariance methods for distribution shift (lesson 11).
    • A lesson in humility. Discovery shows exactly what data alone can and cannot say about causes. This protects you from strong claims based on a learned graph.

    1. Predict first #

    2. The picture #

    2.1 What the data can see: equivalence classes #

    Recall the three junctions of lesson 03. In a chain A→B→CA \to B \to C and in a fork A←B→CA \leftarrow B \to C, the ends AA and CC are dependent, and independent given BB. In a collider A→B←CA \to B \leftarrow C it is the other way round: AA and CC are independent, and dependent given BB.

    So the chain, the reversed chain and the fork imply the same independence facts. A dataset that fits one fits the other two equally well. The collider is different: it is the only DAG on this skeleton with its independence facts.

    Two DAGs are Markov equivalent if they imply the same d-separations, so the same conditional independences. The set of all DAGs that are equivalent to a given one is its Markov equivalence class. The CPDAG (completed partially directed acyclic graph, also called the essential graph) draws a class as one graph: an edge is an arrow if all DAGs in the class orient it the same way, and an undirected line if they disagree.

    Interactive figure: loading…

    Pick a graph or click the edge slots to build your own DAG on 3 or 4 nodes. The second panel is the CPDAG of its class; below are all DAGs of the class (your DAG has a colored frame). Edges that belong to a v-structure are yellow in your DAG. Notice that the chain, the reversed chain and the fork form one class of 3, while the collider is alone. Ignore "Intervene on" for now; section 3.6 uses it.

    The two arrows of a collider whose ends are not adjacent form a v-structure (also called an immorality: two parents that are "not married"). The figure suggests the main theorem of this lesson, proved in section 3.2: two DAGs are Markov equivalent exactly when they have the same skeleton and the same v-structures.

    Quick check

    Which of these DAGs is the only member of its Markov equivalence class?

    2.2 The PC algorithm in pictures #

    If equivalent graphs cannot be separated, the best we can hope for is the class, drawn as a CPDAG. The PC algorithm (named after its authors Peter Spirtes and Clark Glymour) finds it in three phases:

    1. Remove edges. Start with every pair of nodes joined by an undirected edge. For each edge i−ji - j, look for a set SS of neighbors with i⊥ ⁣ ⁣ ⁣⊥j∣Si \perp\!\!\!\perp j \mid S. If you find one, remove the edge and store SS as the separating set sep(i,j)\mathrm{sep}(i, j). Try sets of size 0, then 1, then 2, and so on.
    2. Orient v-structures. For every triple i−k−ji - k - j where ii and jj are not adjacent: if kk is not in sep(i,j)\mathrm{sep}(i, j), orient i→k←ji \to k \leftarrow j.
    3. Propagate. Orient more edges with Meek's rules: an orientation is forced if the other direction would create a new v-structure or a cycle.
    Interactive figure: loading…

    Press Step to run PC one decision at a time. The current step is yellow: the removed edge (dashed) with the nodes of its separating set filled, or the edges being oriented. Start with the oracle tests (exact d-separation in the true graph), then switch to data tests and change nn and α\alpha. In data mode, decisions that differ from the truth are magenta. "Show the true graph" reveals the DAG that made the data, in aqua. Notice that even with perfect tests the edges at AA stay undirected.

    In the sprinkler graph (AA season, BB sprinkler, CC rain, DD wet street, EE slippery street, as in lesson 03), PC with oracle tests ends with A−BA - B, A−CA - C, B→D←CB \to D \leftarrow C and D→ED \to E. Section 4 does every step by hand.

    Quick check

    PC has removed the edge A−CA - C with separating set ∅\varnothing (they are marginally independent). The edges A−BA - B and B−CB - C remain. How does phase 2 orient them?

    2.3 Breaking the symmetry: the shape of the noise #

    Independences cannot orient A−BA - B in the sprinkler graph. Sometimes the shape of the distribution can. Take two variables with y=x+ey = x + e, where the cause xx and the noise ee are independent. Regress yy on xx: the residual is ee, which is independent of xx by construction. Regress xx on yy: least squares makes the residual uncorrelated with yy, but if the noise is not Gaussian, the residual is still dependent on yy. The wrong direction leaves a visible pattern.

    Interactive figure: loading…

    Switch the noise between uniform, Laplace and Gaussian, and draw new samples. Left: the data. Middle: the residual of the causal fit against xx, in aqua. Right: the residual of the anti-causal fit against yy, magenta when a test finds it dependent. With uniform noise the right panel is a diamond: the spread of the residual shrinks at the ends. HSIC (a kernel test of independence) gives a small p-value only in the anti-causal direction. With Gaussian noise both panels look like round clouds: the direction cannot be found this way.

    3. Definitions and main results #

    3.1 The question and its four assumptions #

    The data are samples from PP. We want the DAG GG of the structural causal model that generated them. Discovery needs a bridge between the graph and the distribution:

    1. Causal Markov condition. Every d-separation in GG is a conditional independence in PP: i⊥Gj∣S⇒i⊥ ⁣ ⁣ ⁣⊥j∣Si \perp_G j \mid S \Rightarrow i \perp\!\!\!\perp j \mid S. It holds for every SCM with independent noises (lesson 03, section 3.5).
    2. Faithfulness. The converse: every conditional independence in PP comes from a d-separation, i⊥ ⁣ ⁣ ⁣⊥j∣S⇒i⊥Gj∣Si \perp\!\!\!\perp j \mid S \Rightarrow i \perp_G j \mid S. It fails when effects cancel exactly (lesson 03: a direct effect aa and an indirect effect bcbc with a+bc=0a + bc = 0).
    3. Causal sufficiency. No unmeasured common cause of two measured variables, and no selection of units by a common effect.
    4. Acyclicity. The graph has no directed cycles (no feedback loops in the measured time scale).

    We also need i.i.d. samples and, on finite data, correct test decisions.

    Under assumptions 1–2, the independences of PP are exactly the d-separations of GG. So PP tells us the d-separation list of GG, and nothing more about GG. Which assumptions can data check? Almost none directly. Faithfulness fails only for special parameter values (a set of measure zero for linear Gaussian and for discrete models), but near-cancellation makes tests weak on finite samples. Causal sufficiency is untestable in general; the FCI algorithm (section 3.3) drops it. Acyclicity is a modeling choice. Some violations leave traces: hidden confounders or cycles can produce independence patterns that no DAG explains, and PC may then report conflicting orientations.

    3.2 Markov equivalence #

    Definition. DAGs G1G_1 and G2G_2 on the same nodes are Markov equivalent if i⊥G1j∣S  ⟺  i⊥G2j∣Si \perp_{G_1} j \mid S \iff i \perp_{G_2} j \mid S for all i,j,Si, j, S.

    Theorem (Verma and Pearl 1990). Two DAGs are Markov equivalent if and only if they have the same skeleton and the same v-structures.

    Two facts about a single DAG explain the theorem. They are also the facts PC uses.

    • Fact 1 (adjacency). ii and jj are adjacent in GG if and only if no set SS d-separates them. If they are not adjacent, the parents of one of them separate them: pa(j)\mathrm{pa}(j) if ii is not a descendant of jj, otherwise pa(i)\mathrm{pa}(i).
    • Fact 2 (colliders). Let i−k−ji - k - j with i,ji, j not adjacent. If kk is a collider (i→k←ji \to k \leftarrow j), then no separating set of ii and jj contains kk. If kk is not a collider, every separating set contains kk.
    Why the two facts hold, and the proof of the theorem
    1. If i→ji \to j, the one-edge path i→ji \to j has no middle node, so no set blocks it. So adjacent nodes are never d-separated.
    2. If ii and jj are not adjacent and ii is not a descendant of jj, the local Markov property says that jj is d-separated from its non-descendants by its parents. So S=pa(j)S = \mathrm{pa}(j) separates. (One of the two must be a non-descendant of the other, because GG is acyclic.)
    3. Fact 2, collider case: the path i→k←ji \to k \leftarrow j is open whenever kk or one of its descendants is in SS. So a separating set contains neither kk nor its descendants.
    4. Fact 2, non-collider case: the path i−k−ji - k - j through a chain or fork is open unless k∈Sk \in S. So every separating set contains kk.
    5. "Only if" of the theorem: by Fact 1 the d-separation list fixes the skeleton. By Fact 2 it fixes, for every unshielded triple, whether the middle node is a collider. So equivalent DAGs share the skeleton and the v-structures.
    6. "If" is the harder direction: same skeleton and v-structures imply the same d-separation list. The proof shows that a path is open given SS in G1G_1 exactly when a corresponding path is open in G2G_2; see Verma and Pearl (1990) or Pearl (2009), theorem 1.2.8.

    Counting. On 3 labeled nodes there are 25 DAGs and 11 equivalence classes:

    Skeleton DAGs Classes Class sizes
    no edge 1 1 1
    one edge (3 choices) 3⋅2=63 \cdot 2 = 6 3 2 each (A→BA \to B and A←BA \leftarrow B)
    two edges (3 choices of middle node) 3⋅4=123 \cdot 4 = 12 6 3 (chains and fork) and 1 (collider)
    complete 6 1 6 (no v-structure is possible)
    total 25 11

    The complete graph has 6 DAGs, one per ordering of the 3 nodes: with all three edges present, the data show no independence at all. On 4 nodes there are 543 DAGs in 185 classes; on 10 nodes about 4.2⋅10184.2 \cdot 10^{18} DAGs. Exhaustive search is impossible beyond a handful of nodes.

    3.3 The PC algorithm #

    Theorem (Spirtes, Glymour and Scheines). Under causal Markov, faithfulness, causal sufficiency and acyclicity, and with correct independence decisions, the PC algorithm returns the CPDAG of the true DAG.

    The three phases, with the reason for each:

    PC(variables 1..d, test):
      C := complete undirected graph;  level := 0
      repeat                                      # phase 1: skeleton
        for each edge i - j in C:
          for each S ⊆ adj_C(i) \ {j} with |S| = level:      # also the same with i and j swapped
            if test says i ⊥⊥ j | S:  remove i - j;  sep(i, j) := S;  break
        level := level + 1
      until no node has more than `level` other neighbours
      for each triple i - k - j with i, j not adjacent:       # phase 2: v-structures
        if k ∉ sep(i, j):  orient i → k ← j
      apply Meek's rules R1-R3 until nothing changes         # phase 3
    
    • Phase 1 relies on Fact 1. A non-adjacent pair is separated by the parents of one of them, and the parents are among the neighbors, so searching subsets of the current neighbors is enough. It starts with small sets because they are cheap and their tests are more reliable.
    • Phase 2 relies on Fact 2. One separating set is enough: if kk is not in it, kk is a collider.
    • Phase 3 orients the edges that every DAG of the class shares:
    Rule If you see Then orient Otherwise you would create
    R1 a→b−ca \to b - c, with a,ca, c not adjacent b→cb \to c a new v-structure a→b←ca \to b \leftarrow c
    R2 a→c→ba \to c \to b and a−ba - b a→ba \to b a cycle a→c→b→aa \to c \to b \to a
    R3 a−c→ba - c \to b, a−e→ba - e \to b, a−ba - b, with c,ec, e not adjacent a→ba \to b a cycle or a new v-structure (check both cases of b→ab \to a)

    Meek (1995) proved that R1–R3 orient exactly the edges that are directed in the CPDAG. (A fourth rule is needed only when you add background knowledge, such as "BB cannot cause AA".)

    The version used here. In the original PC, the result of phase 1 can depend on the order in which pairs are tested, because removing one edge changes the neighbor sets of later tests. PC-stable (Colombo and Maathuis 2014) freezes the neighbor sets at the start of each level, so the order no longer matters. The widget and the notebook use PC-stable.

    Cost. The number of tests grows like dqd^{q}, where qq is the largest number of neighbors of a node. PC is fast on sparse graphs with hundreds of variables and slow on dense ones.

    Independence tests on data. For (approximately) Gaussian data, i⊥ ⁣ ⁣ ⁣⊥j∣Si \perp\!\!\!\perp j \mid S is equivalent to a zero partial correlation ρij⋅S\rho_{ij \cdot S}. With PP the inverse of the correlation matrix of the variables (i,j,S)(i, j, S),

    ρ^ij⋅S=−PijPiiPjj,z=12ln⁡1+ρ^1−ρ^⋅n−∣S∣−3.\hat\rho_{ij \cdot S} = -\frac{P_{ij}}{\sqrt{P_{ii} P_{jj}}}, \qquad z = \tfrac12 \ln\frac{1 + \hat\rho}{1 - \hat\rho} \cdot \sqrt{n - |S| - 3} .

    Under independence, zz is approximately N(0,1)\mathcal N(0, 1) (Fisher's z test). PC accepts independence when the p-value is larger than the level α\alpha. For nonlinear or discrete data, use a test of conditional mutual information or a kernel test (link: information theory 07).

    Quick check

    You rerun PC on the same data with α=0.2\alpha = 0.2 instead of α=0.01\alpha = 0.01. What do you expect?

    Hidden common causes: FCI. If causal sufficiency fails, PC can return an edge X−YX - Y for two variables that only share a hidden cause. The FCI algorithm (Fast Causal Inference, Spirtes et al.) allows hidden common causes and selection. Its output, a partial ancestral graph, marks each edge end as an arrowhead, a tail or a circle ("cannot decide"); X↔YX \leftrightarrow Y means "a hidden common cause, and no causal effect either way". It needs more tests and usually orients fewer edges.

    3.4 Score-based methods and continuous optimization #

    Instead of tests, give every graph a score and search for the best one. For linear-Gaussian or discrete models a standard score is the BIC:

    BIC(G)=∑j=1dlog⁡p^(xj∣xpa(j))−kG2log⁡n,\mathrm{BIC}(G) = \sum_{j=1}^d \log \hat p\big(x_j \mid x_{\mathrm{pa}(j)}\big) - \frac{k_G}{2} \log n ,

    where the first term is the maximized log-likelihood of each variable given its parents and kGk_G the number of parameters. The penalty prefers sparse graphs. Markov-equivalent DAGs get the same BIC (score equivalence), so scores also cannot go beyond the class.

    Greedy equivalence search (GES, Chickering 2002) moves between equivalence classes: a forward phase adds edges while the score improves, a backward phase removes them. With a consistent, score-equivalent score, GES finds the true class as n→∞n \to \infty (under faithfulness), although it looks at only a tiny part of the search space.

    NOTEARS (Zheng et al. 2018) turns the combinatorial search into a smooth problem. For a linear SCM X=XW+NX = XW + N with a weighted adjacency matrix W∈Rd×dW \in \R^{d \times d}:

    min⁡W 12n∥X−XW∥F2+λ∥W∥1subject toh(W)=tr⁡(eW∘W)−d=0,\min_{W} \ \frac{1}{2n} \|X - XW\|_F^2 + \lambda \|W\|_1 \quad \text{subject to} \quad h(W) = \operatorname{tr}\big(e^{W \circ W}\big) - d = 0 ,

    where W∘WW \circ W squares each entry and eA=∑k≥0Ak/k!e^{A} = \sum_{k \ge 0} A^k / k! is the matrix exponential.

    Theorem. h(W)≥0h(W) \ge 0, and h(W)=0h(W) = 0 if and only if the graph of WW (an edge i→ji \to j when Wij≠0W_{ij} \neq 0) is acyclic.

    Proof
    1. Let A=W∘WA = W \circ W. All entries of AA are ≥0\ge 0, and Aij>0A_{ij} > 0 exactly when i→ji \to j is an edge.
    2. (Ak)ii(A^k)_{ii} is a sum over all closed walks i→⋯→ii \to \dots \to i of length kk of the products of the AA entries along the walk. Each product is ≥0\ge 0, and it is >0> 0 when all edges of the walk exist. So tr⁡(Ak)>0\operatorname{tr}(A^k) > 0 if the graph has a closed walk of length kk, and =0= 0 otherwise.
    3. tr⁡(eA)=tr⁡(I)+∑k≥1tr⁡(Ak)/k!=d+∑k≥1tr⁡(Ak)/k!\operatorname{tr}(e^A) = \operatorname{tr}(I) + \sum_{k \ge 1} \operatorname{tr}(A^k)/k! = d + \sum_{k \ge 1} \operatorname{tr}(A^k)/k!. So h(W)=∑k≥1tr⁡(Ak)/k!≥0h(W) = \sum_{k \ge 1} \operatorname{tr}(A^k)/k! \ge 0.
    4. A graph has a directed cycle if and only if it has a closed walk of some length k≥1k \ge 1 (a self-loop is the case k=1k = 1). So h(W)=0h(W) = 0 exactly for acyclic graphs. ■\blacksquare

    Small example. For two nodes with W=(0ab0)W = \begin{pmatrix} 0 & a \\ b & 0 \end{pmatrix} (edges 1→21 \to 2 with weight aa, 2→12 \to 1 with weight bb), A2=a2b2IA^2 = a^2 b^2 I and the odd powers have zero trace, so h(W)=2cosh⁡(ab)−2h(W) = 2\cosh(ab) - 2. It is zero exactly when a=0a = 0 or b=0b = 0. For a=1a = 1, b=0.5b = 0.5: h=2cosh⁡(0.5)−2≈0.255h = 2\cosh(0.5) - 2 \approx 0.255. The gradient ∇h(W)=(eW∘W)⊤∘2W\nabla h(W) = (e^{W \circ W})^\top \circ 2W lets a solver (an augmented Lagrangian method in the paper) push hh to zero. The problem is not convex, so the solver finds a local solution, and small weights are set to zero at the end.

    The varsortability warning. NOTEARS and its successors did very well on simulated benchmarks. Reisach, Seiler and Weichwald (2021) found a reason that has nothing to do with causal reasoning. In the usual simulations (weights with ∣w∣∈[0.5,2]|w| \in [0.5, 2], equal noise variances), the marginal variance grows along the causal order: each effect adds its own noise to the variance it receives from its causes. A trivial baseline, "sort the variables by variance, then regress each one on the earlier ones", is as good as NOTEARS there. After standardizing each variable to variance 1, these methods get much worse, although standardizing changes no causal fact.

    Interactive figure: loading…

    Draw new random graphs, change the weight range, then switch the data to "standardized". The bars show the exact variance of each variable in the causal order (log scale); nodes without parents are aqua. Varsortability is the share of cause → effect pairs in which the cause has the smaller variance; it turns magenta when it is above 0.75. Notice that it is high for every weight range, because every non-root variable has its own unit noise plus the noise of its causes. After standardizing it is 0.5: the variance no longer tells the order.

    The practical lesson: a discovery method should give the same graph when you change the units of a variable (metres or kilometres). Check this by running it on standardized data too.

    3.5 Beyond equivalence: functional assumptions #

    Markov equivalence is a limit for methods that use only conditional independences. A method that assumes more about the functions or the noise can sometimes orient every edge.

    LiNGAM (Linear Non-Gaussian Acyclic Model; Shimizu et al. 2006): X=BX+eX = BX + e with BB the weights of a DAG and e1,…,ede_1, \dots, e_d independent and non-Gaussian. Then BB, and so the full DAG, is identifiable from PP. The original algorithm uses independent component analysis (ICA): X=(I−B)−1eX = (I - B)^{-1}e is a mixture of independent non-Gaussian sources, and ICA can recover the mixing matrix.

    Two variables, why it works. Let y=x+ey = x + e with x⊥ ⁣ ⁣ ⁣⊥ex \perp\!\!\!\perp e, both with variance 1. The backward regression of xx on yy has slope b=Cov⁡(x,y)/Var⁡(y)=1/2b = \operatorname{Cov}(x, y)/\operatorname{Var}(y) = 1/2, so its residual is

    r=x−12y=12x−12e,y=x+e.r = x - \tfrac12 y = \tfrac12 x - \tfrac12 e, \qquad y = x + e .

    Both rr and yy are linear combinations of the independent variables xx and ee, with all four coefficients nonzero. The Darmois–Skitovich theorem says: if two such linear combinations are independent, then every variable that appears in both is Gaussian. So if xx or ee is not Gaussian, rr and yy are dependent, although they are uncorrelated. In the causal direction the residual is ee, which is independent of xx. The asymmetry identifies the direction. With uniform noise you can see the dependence: when yy is near its largest value 232\sqrt3, both xx and ee must be near 3\sqrt3, so r=(x−e)/2r = (x - e)/2 must be near 0. This is the diamond in the figure of section 2.3.

    Additive noise models (Hoyer et al. 2009) extend the idea to nonlinear functions: y=f(x)+ey = f(x) + e with e⊥ ⁣ ⁣ ⁣⊥xe \perp\!\!\!\perp x. For generic nonlinear ff, no model x=g(y)+e~x = g(y) + \tilde e with e~⊥ ⁣ ⁣ ⁣⊥y\tilde e \perp\!\!\!\perp y exists. The test is the same: fit both directions with a regression method and keep the direction whose residual is independent of the input. The linear-Gaussian case is one of the few exceptions where both directions fit.

    Quick check

    You run the two-direction test on y=2x+ey = 2x + e with xx and ee both Gaussian. What do you find?

    Gaussian noise with equal variances is identifiable too

    Peters and Bühlmann (2014) proved that a linear-Gaussian SCM is identifiable if all noise variances are equal (the common value σ2\sigma^2 may be unknown). One way to see it: take the variables in some order and compute the conditional variance of each one given the earlier ones. In a causal order each of these equals σ2\sigma^2. In every order their product is det⁡Σ\det \Sigma, so by the inequality of arithmetic and geometric means their sum is at least d (det⁡Σ)1/d=dσ2d\,(\det\Sigma)^{1/d} = d\sigma^2, with equality only when they are all equal. The theorem says that a non-causal order cannot make them all equal. So the orders with the smallest sum of conditional variances are exactly the causal orders. This assumption is strong and not invariant to rescaling. It is closely related to the varsortability warning: equal noise variances in the raw units are exactly what makes simulated variances grow along the order.

    3.6 Interventions shrink the class #

    An experiment changes the picture. A perfect intervention do(B=b)\mathrm{do}(B = b) cuts all arrows into BB (lesson 04). Compare the observational and the interventional data:

    • If A→BA \to B, setting BB by hand does not change the distribution of AA.
    • If B→AB \to A, setting BB changes the distribution of AA.

    So one experiment on BB orients every edge at BB. Then Meek's rules may orient more. Two DAGs that also agree on the edges at every intervened node form an interventional equivalence class (Hauser and Bühlmann 2012). Eberhardt, Glymour and Scheines (2005) showed that d−1d - 1 experiments on single variables are always enough to orient every edge among dd variables, and in the worst case necessary.

    Interactive figure: loading…

    Choose the node to intervene on (blue). The chain A→B→C→DA \to B \to C \to D has a class of 4 DAGs. An experiment on BB orients A→BA \to B and B→CB \to C, and then rule R1 orients C→DC \to D: the class has one member. An experiment on CC leaves A−BA - B open (2 DAGs). Try the diamond: an experiment on DD orients nothing new, because its edges are already in the v-structure.

    Quick check

    The true graph is A→B→CA \to B \to C, and you can run one experiment on one variable. Which choice does not orient both edges?

    3.7 Honest status of causal discovery #

    • Hidden variables, measurement error and selection break causal sufficiency and the meaning of the tests. Most real datasets have all three.
    • Tests are weak for conditioning sets that are large compared with nn, and for nonlinear dependence; one wrong test can change many edges.
    • Different methods disagree on real data, and benchmark results on simulated data can be misleading (varsortability).
    • Good use: generate hypotheses, combine the output with domain knowledge (time order, known absences of effects), report the equivalence class rather than one DAG, check the stability of the edges under resampling (bootstrap), and confirm important edges with experiments.

    Answers to the predictions. (1) No. The chain and the reversed chain have the same skeleton and no v-structure, so they imply the same independences; no amount of observational data separates them without extra assumptions. (2) The collider A→B←CA \to B \leftarrow C: it is the only graph in which AA and CC are independent but dependent given BB. (3) Yes. With uniform noise the residual of the anti-causal regression is uncorrelated with yy but not independent of it (section 3.5).

    4. Worked example #

    Run PC with oracle tests on the sprinkler graph: A→BA \to B, A→CA \to C, B→DB \to D, C→DC \to D, D→ED \to E (AA season, BB sprinkler, CC rain, DD wet, EE slippery). The oracle answers each test with d-separation in the true graph.

    Phase 1, level 0 (S=∅S = \varnothing). Test all (52)=10\binom52 = 10 pairs for marginal independence. Every pair is joined by an open path (for example B←A→CB \leftarrow A \to C for B,CB, C and A→B→D→EA \to B \to D \to E for A,EA, E). No edge is removed.

    Phase 1, level 1 (∣S∣=1|S| = 1). All neighbor sets are still complete. The tests that succeed:

    Pair Separating set Why the paths are blocked
    A,EA, E {D}\{D\} every path from AA to EE ends with D→ED \to E, and DD is a non-collider in SS
    B,CB, C {A}\{A\} B←A→CB \leftarrow A \to C is blocked by AA; B→D←CB \to D \leftarrow C is blocked at the collider DD
    B,EB, E {D}\{D\} every path ends with D→ED \to E
    C,EC, E {D}\{D\} the same

    A,DA, D survives level 1: given BB the path A→C→DA \to C \to D is open, and given CC the path A→B→DA \to B \to D is open. Given EE both are open. After level 1 the graph is A−BA - B, A−CA - C, A−DA - D, B−DB - D, C−DC - D, D−ED - E.

    Phase 1, level 2. Now adj(A)∖{D}={B,C}\mathrm{adj}(A) \setminus \{D\} = \{B, C\}. The test A⊥ ⁣ ⁣ ⁣⊥D∣{B,C}A \perp\!\!\!\perp D \mid \{B, C\} succeeds: both directed paths are blocked, and there is no other path. Remove A−DA - D with sep(A,D)={B,C}\mathrm{sep}(A, D) = \{B, C\}. The remaining five edges are true edges; no set separates them. Level 3 is not possible (no node has 4 other neighbors left), so phase 1 ends. The skeleton is now exactly the true skeleton.

    Phase 2. Check every unshielded triple (two ends not adjacent):

    Triple sep\mathrm{sep} of the ends Middle node in it? Result
    B−A−CB - A - C {A}\{A\} yes no v-structure
    B−D−CB - D - C {A}\{A\} no B→D←CB \to D \leftarrow C
    A−B−DA - B - D {B,C}\{B, C\} yes nothing
    A−C−DA - C - D {B,C}\{B, C\} yes nothing
    B−D−EB - D - E, C−D−EC - D - E {D}\{D\} yes nothing

    Phase 3. R1 on B→D−EB \to D - E (BB and EE not adjacent) gives D→ED \to E. No arrow points into AA, BB or CC, so no rule applies to A−BA - B and A−CA - C.

    Result. The CPDAG is A−BA - B, A−CA - C, B→D←CB \to D \leftarrow C, D→ED \to E. Its class has 3 DAGs:

    1. A→BA \to B, A→CA \to C (the truth);
    2. B→A→CB \to A \to C;
    3. C→A→BC \to A \to B.

    The fourth orientation, B→A←CB \to A \leftarrow C, is not in the class: it adds a v-structure at AA (BB and CC are not adjacent). The data cannot say whether the season drives the sprinkler or the other way round. Background knowledge does: nothing in the graph can cause the season, so arrows into AA are impossible, and only DAG 1 remains.

    PC needed only 5 successful tests for the removals, but it ran 78 tests in total (the widget counts them). On data, each of the 78 can be wrong.

    5. In code #

    NumPy and SciPy count the equivalence classes, run the key Fisher-z tests of the worked example on simulated linear-Gaussian data, and test the two directions of y=x+ey = x + e.

    import itertools
    import numpy as np
    from scipy import stats
    
    # (a) count DAGs and Markov equivalence classes on 3 labelled nodes
    pairs = list(itertools.combinations(range(3), 2))
    def acyclic(E):                      # E: set of directed edges (i, j)
        return not any((a, b) in E and (b, c) in E and (c, a) in E for a, b, c in itertools.permutations(range(3)))
    def key(E):                          # skeleton + v-structures
        skel = frozenset(frozenset(e) for e in E)
        vs = frozenset((frozenset((i, j)), k) for (i, k), (j, k2) in itertools.permutations(E, 2)
                       if k == k2 and i != j and frozenset((i, j)) not in skel)
        return skel, vs
    dags = []
    for s in itertools.product((0, 1, 2), repeat=3):
        E = {(i, j) if v == 1 else (j, i) for (i, j), v in zip(pairs, s) if v}
        if acyclic(E):
            dags.append(E)
    print(len(dags), len({key(E) for E in dags}))                       # 25 11
    
    # (b) Fisher-z tests on the sprinkler SCM: A season, B sprinkler, C rain, D wet, E slippery
    W = np.zeros((5, 5))
    for (i, j), w in {(0, 1): 0.8, (0, 2): 0.7, (1, 3): 0.9, (2, 3): 0.8, (3, 4): 0.7}.items():
        W[i, j] = w
    rng = np.random.default_rng(0)
    n = 1000
    X = np.zeros((n, 5))
    for j in range(5):                                                   # nodes are in causal order
        X[:, j] = X @ W[:, j] + rng.normal(size=n)
    C = np.corrcoef(X.T)
    
    def fisher_z(i, j, S):
        idx = [i, j, *S]
        P = np.linalg.inv(C[np.ix_(idx, idx)])
        r = -P[0, 1] / np.sqrt(P[0, 0] * P[1, 1])                       # partial correlation
        z = np.arctanh(r) * np.sqrt(n - len(S) - 3)
        return r, 2 * stats.norm.sf(abs(z))
    
    for i, j, S in [(1, 2, []), (1, 2, [0]), (0, 4, [3]), (0, 3, [1]), (0, 3, [1, 2])]:
        r, p = fisher_z(i, j, S)
        print("ABCDE"[i], "ABCDE"[j], ["ABCDE"[s] for s in S], round(r, 3), round(p, 3))
    # B C [] 0.35 0.0
    # B C ['A'] 0.008 0.809
    # A E ['D'] -0.055 0.08
    # A D ['B'] 0.289 0.0
    # A D ['B', 'C'] 0.001 0.968
    
    # (c) the direction of y = x + e with uniform noise: residual independent of the regressor?
    def hsic(a, b):
        K = np.exp(-(a[:, None] - a[None]) ** 2 / np.median((a[:, None] - a[None]) ** 2))
        L = np.exp(-(b[:, None] - b[None]) ** 2 / np.median((b[:, None] - b[None]) ** 2))
        H = np.eye(len(a)) - 1 / len(a)
        return np.sum((H @ K @ H) * L) / len(a) ** 2
    x = rng.uniform(-np.sqrt(3), np.sqrt(3), 400)
    y = x + rng.uniform(-np.sqrt(3), np.sqrt(3), 400)
    res_y = y - np.polyval(np.polyfit(x, y, 1), x)                       # fit y from x
    res_x = x - np.polyval(np.polyfit(y, x, 1), y)                       # fit x from y
    print(round(np.corrcoef(x, res_y)[0, 1], 3), round(np.corrcoef(y, res_x)[0, 1], 3))   # 0.0 0.0
    print(round(1000 * hsic(x, res_y), 2), round(1000 * hsic(y, res_x), 2))              # 0.62 6.54
    

    The tests agree with the worked example: BB and CC are dependent but independent given AA, and AA and DD are independent only given both BB and CC. The test of A,EA, E given DD has p=0.08p = 0.08: with α=0.1\alpha = 0.1 it would count as dependent. (PC still recovers the correct CPDAG on this sample for every α\alpha from 0.01 to 0.2: when this test keeps A−EA - E, another test with a different separating set removes it.) In (c) both residuals are uncorrelated with their regressor, but the HSIC statistic is ten times larger in the anti-causal direction. The notebook builds the full PC algorithm and a permutation test.

    6. Where it shows up #

    • Molecular biology. Sachs et al. (2005) learned a protein signalling network in human immune cells from flow-cytometry data with interventions; it is still a standard test case. Gene regulatory network inference uses constraint-based and score-based methods on expression data, plus knock-out experiments as interventions.
    • Climate and Earth science. PCMCI (Runge et al. 2019) adapts PC to time series, where time order fixes most directions, to find drivers of climate patterns such as monsoons and El Niño teleconnections.
    • Neuroscience. Effective connectivity between brain regions is estimated from fMRI and electrophysiology data with discovery methods, with all the caveats of hidden variables and measurement error.
    • Industry. Root-cause analysis in microservice systems and manufacturing lines learns a graph of metrics, then follows the arrows back from an anomaly. Feature selection uses the Markov blanket (parents, children and the children's other parents) of the target, which PC-like algorithms find.
    • Causal ML. Discovery gives candidate graphs for the adjustment of lessons 04–08; the equivalence class shows whether an adjustment set is valid in every graph of the class.

    7. Common mistakes #

    • Reading every arrow of a learned graph as causal. Undirected edges of a CPDAG can point either way, and an arrow is only as good as the tests and the assumptions behind it. Report the class and say which assumptions you made.
    • Forgetting hidden confounders. PC assumes causal sufficiency. With a hidden common cause it draws an edge between two variables that have no effect on each other. Use FCI when hidden confounding is plausible, and check whether edges survive.
    • Treating "not rejected" as "independent" without power. A small nn removes true edges. Check the stability of each edge under bootstrap resampling and with several values of α\alpha.
    • Using the wrong test. Fisher's z test only sees linear dependence. For nonlinear or discrete data, use a kernel or conditional mutual information test, or the result can miss real edges.
    • Trusting results that change with the units. If standardizing the data changes the graph, the method used the scale (varsortability), not the causal structure.
    • Ignoring time order and domain knowledge. A cause cannot come after its effect. Add such constraints as background knowledge; they orient edges that the data cannot.
    • Selecting units by an outcome before discovery. Selection on a common effect creates dependences (Berkson's paradox, lesson 03) that discovery then reads as edges.

    8. Check yourself #

    1. How many DAGs are in the Markov equivalence class of the chain A→B→C→DA \to B \to C \to D? Draw its CPDAG.
      Answer4. The CPDAG is A−B−C−DA - B - C - D with all edges undirected. A DAG in the class must have no v-structure, so at most one arrow can point into each node from the path; such an orientation is fixed by choosing the one node where all arrows start (the source): AA, BB, CC or DD.
    2. Why does phase 1 of PC only test conditioning sets made of current neighbors of ii or of jj?
      AnswerIf ii and jj are not adjacent in the true DAG, the parents of one of them separate them (Fact 1). Parents are neighbors in the true graph, and the current graph of PC always contains the true skeleton (with correct tests), so some subset of the current neighbors is enough.
    3. Compute h(W)=tr⁡(eW∘W)−dh(W) = \operatorname{tr}(e^{W \circ W}) - d for W=(0100)W = \begin{pmatrix} 0 & 1 \\ 0 & 0 \end{pmatrix} and for W=(010.50)W = \begin{pmatrix} 0 & 1 \\ 0.5 & 0 \end{pmatrix}.
      AnswerFirst: A=W∘WA = W \circ W is nilpotent (A2=0A^2 = 0), so eA=I+Ae^A = I + A, trace 2, h=0h = 0: a DAG. Second: A2=0.25 IA^2 = 0.25\,I, so tr⁡(eA)=2cosh⁡(0.5)≈2.255\operatorname{tr}(e^A) = 2\cosh(0.5) \approx 2.255 and h≈0.255>0h \approx 0.255 > 0: the graph has the cycle 1→2→11 \to 2 \to 1.
    4. In the sprinkler class (section 4), which DAG is left if you know that nothing in the graph causes the season? Without this knowledge, how many DAGs are left after one experiment on BB?
      AnswerWith the background knowledge only the true DAG is left, because the other two members have an arrow into AA. The experiment on BB orients A→BA \to B (and B→DB \to D, already oriented). No Meek rule then applies to A−CA - C, because no arrow points into AA. So A−CA - C stays undirected and 2 DAGs are left: A→CA \to C and C→AC \to A.
    5. Two variables XX and YY have a hidden common cause and no effect on each other. What does PC return, and what does FCI return?
      AnswerPC returns the edge X−YX - Y: they are dependent and there is no other variable to condition on. FCI returns XX o–o YY (with only two variables it cannot decide); with suitable other variables it can return X↔YX \leftrightarrow Y, which says "hidden common cause, no direct effect".
    6. A colleague runs NOTEARS on raw sensor data and gets a clean chain. On standardized data the chain is reversed. What do you conclude?
      AnswerThe result depends on the scale, so it is probably driven by the variances (varsortability), not by causal structure. Units are a convention and cannot carry causal information unless a strong assumption (such as equal noise variances in these units) is justified. Report the result as unreliable and try scale-invariant methods (PC with partial correlations, GES with BIC) or experiments.
    Recall cardsAnswer in your head first, then reveal. Add the cards to your review deck to see them again just before you would forget.
    1. What does faithfulness assume?

    2. When are two DAGs Markov equivalent?

    3. What is a CPDAG?

    4. How many DAGs and Markov equivalence classes are there on 3 labeled nodes?

    5. What are the three phases of the PC algorithm?

    6. In PC, when is i−k−ji - k - j (with i,ji, j not adjacent) oriented as i→k←ji \to k \leftarrow j?

    7. What is the NOTEARS acyclicity function, and why is it zero exactly for DAGs?

    8. Why can LiNGAM orient an edge that PC cannot?

    9. What is the varsortability warning?

    10. Why does an intervention on BB help discovery?

    Open the review →

    9. Interview angle #

    • "Can you learn causal structure from observational data?" — Partly. Under causal Markov, faithfulness, no hidden confounders and acyclicity, you can learn the Markov equivalence class (skeleton plus v-structures, then Meek's rules), not a single DAG. Orienting the rest needs extra assumptions (non-Gaussian or nonlinear additive noise), background knowledge or experiments.
    • "How does the PC algorithm work, and what can go wrong?" — Three phases (skeleton by independence tests, v-structures from separating sets, Meek's rules). On finite data, a missed dependence deletes a true edge, a wrong separating set creates a wrong v-structure, and errors propagate. Hidden confounders produce spurious edges; FCI handles them.
    • "Would you trust NOTEARS on our data?" — Only with care: the problem is non-convex, the least squares score depends on the scale, and benchmark wins can come from varsortability. Run it on standardized data, compare with PC or GES, check stability under bootstrap, and validate key edges with experiments or domain knowledge.
    • "A learned graph says feature X causes churn. What next?" — Treat it as a hypothesis. Check the assumptions (hidden confounders, selection), the stability of the edge, and whether its direction is decided by the data or only by the method; then test it with an A/B experiment.

    10. Practice #

    The notebook for this lesson has five exercises, each with a check cell and a folded solution:

    1. Write fisher_z_test(C, n, i, j, S): partial correlation from the correlation matrix and the p-value of Fisher's z test.
    2. Write pc_skeleton and orient_v_structures (Meek's rules are given) and measure the structural Hamming distance to the true CPDAG of a 5-node linear-Gaussian SCM for several nn and α\alpha.
    3. Enumerate the Markov equivalence class of a small DAG by brute force and check the counts 25 and 11 on 3 nodes.
    4. Test the direction of y=x+ey = x + e with non-Gaussian noise: regress both ways and compare a simple HSIC permutation test of residual and regressor.
    5. Varsortability: show that "sort by variance" finds the causal order on standard simulated data and fails after standardizing.

    11. Watch and read #

    The full lecture: assumptions, Markov equivalence, PC, and the semi-parametric (non-Gaussian, additive noise) setting.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    The three phases of PC on a small example, as in section 4.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    Why non-Gaussian noise identifies the direction of a linear edge.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    How experiments shrink the equivalence class, and how many are needed.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    LiNGAM and its extensions, presented by its first author.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    The machine-learning view of causal models and why independence of mechanisms helps discovery.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play