Causal graphs: structural causal models, d-separation, confounders and colliders
Before this lesson: lesson 01 (association and its five sources) and lesson 02 (potential outcomes). From probability: conditional probability and conditional independence (probability 04); the linear-Gaussian examples use covariance and correlation (probability 05 helps). Time: about 90 minutes, plus the notebook.
Why this matters #
- Which variables to control for. Every causal estimate from observational data (lessons 04–08) starts with the question "what do I adjust for?". The answer is a graph question: adjust so that every non-causal path is blocked and no new path is opened. "Control for everything" is wrong, and this lesson shows why.
- Selected data. Many ML datasets are filtered by a common effect: loans exist only for approved applicants, ratings only for items a user chose to watch, hospital records only for people who came to the hospital. Filtering on a common effect creates associations that do not exist in the population (Berkson's paradox).
- Learning graphs from data. Causal discovery algorithms (lesson 10) test conditional independences and use d-separation to rule out graphs.
- Probabilistic models. Bayesian networks, the graphical models behind many probabilistic ML methods, use the same factorization and the same d-separation rule (Koller and Friedman, ch. 3).
1. Predict first #
2. The picture #
2.1 A model you can run #
A structural causal model (SCM) is a small program that generates the data. Each variable gets one assignment that computes it from its direct causes and from its own random noise. Here is the classic sprinkler example (Pearl 2009, ch. 1):
season := U_season
sprinkler := f_sprinkler(season, U_sprinkler)
rain := f_rain(season, U_rain)
wet := f_wet(sprinkler, rain, U_wet)
slippery := f_slippery(wet, U_slippery)
The symbol means assignment, not equality: the right side causes the left side. If we
change the sprinkler by hand, wet changes, but season does not. The noises are independent of
each other; they stand for all the small causes that the model does not name.
The causal graph of the SCM has one node per variable and an arrow when appears on the right side of the assignment of . The inputs of a node are its parents. Here the graph is Season → Sprinkler, Season → Rain, Sprinkler → Wet, Rain → Wet, Wet → Slippery.
- An arrow claims a possible direct effect. The function may also ignore that input; then the arrow is there but does nothing.
- A missing arrow claims no direct effect. Season → Wet is missing: the model says that the season changes how wet the street is only through the sprinkler and the rain. This is the strong part of a graph, and it is what makes the graph testable.
- The graph must be a directed acyclic graph (DAG): following arrows you can never come back to where you started. A variable cannot be its own cause.
2.2 Three junctions #
Every path in a graph is made of steps through middle nodes. There are only three ways a path can pass through a middle node :
| Junction | Drawing | Example | and | and given |
|---|---|---|---|---|
| chain | smoking → tar → cancer | dependent | independent | |
| fork (common cause) | ice cream ← heat → drownings | dependent | independent | |
| collider (common effect) | talent → hired ← looks | independent | dependent |
"Given " means conditioning on : we look at units with the same value of (a slice of the data), put into a regression, or keep only units selected by . The first two rows feel natural. The collider row is the surprise: two independent causes become dependent once we know their common effect.
Switch between chain, fork and collider, then press "Condition on B". The scatter shows against for 300 units from the model in the panel. When you condition, only the units in a narrow slice of stay bright (yellow), with their own fitted line. In the chain and the fork the slice is a round cloud (the association disappears). In the collider the full cloud is round but the slice slopes down. The panel shows the sample correlation next to the exact value.
Why the collider works this way. Use the coin example. Before you hear anything, coin tells you nothing about coin . Now you know the bell rang (): at least one coin is heads. If is tails, then must be heads. Knowing the common effect links the two causes: one cause "explains away" the other.
2.3 Selection is conditioning #
Keeping only some units is a way of conditioning. If the selection depends on a common effect, it creates an association between its causes. This is Berkson's paradox (Berkson 1946, with hospital patients) or collider bias.
Move the share selected from 100% down to 5%. Each dot is a person with two independent traits. The yellow dots are selected because the sum of the traits is in the top share; the dashed magenta line is the selection threshold. The correlation in the whole sample stays near 0, but inside the selection it becomes more and more negative: about when half are selected and about for the top 10%. The exact value comes from the truncated normal distribution.
Step by step, why the hired actors show a negative correlation.
- The studio hires people with talent + looks above a threshold.
- Take a hired actor with low talent. To pass the threshold, this actor needs high looks.
- Take a hired actor with high talent. This actor passes with any looks.
- So, among hired actors, low talent goes with high looks more often than high talent does: a negative correlation that does not exist in the population.
A school admits students whose test score + interview score is high. In the applicant pool the two scores are independent. A researcher uses only admitted students and finds a negative correlation between the two scores. What is the best explanation?
Admission is a collider: test score → admitted ← interview. Selecting on it opens the path between its causes. More data does not help: the negative correlation is a property of the selected population, not noise. A common cause would create a positive association in the whole pool, which the question rules out.
2.4 Paths, and when they are blocked #
In a bigger graph, two nodes are linked by several paths. A path is a sequence of nodes where each neighbor pair is joined by an arrow, in either direction, and no node repeats. For example, in the sprinkler graph, Sprinkler ← Season → Rain is a path, and so is Sprinkler → Wet ← Rain.
Association can flow along a path only if every middle node lets it through. Each middle node is one of the three junctions, so the table of section 2.2 decides it:
- a chain or fork node lets association through unless we condition on it;
- a collider node blocks unless we condition on it or on one of its descendants.
A path is open if every middle node lets association through; otherwise it is blocked. If every path between and is blocked, they are d-separated ("d" for directional).
Pick two nodes with "From" and "To", then click nodes to put them into the conditioning set (yellow). The panel lists every path between the two nodes and says why each one is open or blocked. Open non-causal paths are thick magenta, open directed paths are thick aqua; dashed violet nodes are unobserved. Try Sprinkler and Rain with , then add Wet. Use the other graphs, or build your own with "add node" and "add edge".
In the sprinkler graph, Sprinkler and Rain are d-separated given . You add Slippery to the conditioning set. What happens?
The path Sprinkler → Wet ← Rain has the collider Wet. A collider lets association through when we condition on it or on any descendant of it. Slippery is a child of Wet, so conditioning on Slippery opens this path. Season blocks only the other path, Sprinkler ← Season → Rain.
3. Definitions and main results #
3.1 Structural causal models and graphs #
Definition (SCM). A structural causal model has
- endogenous variables (the variables we model);
- exogenous noises , jointly independent, with a distribution ;
- one structural assignment per variable, , where the parents are some of the other endogenous variables.
Its causal graph has an arrow from each parent to its child. We assume is a DAG. Then the assignments can be run in a topological order (parents before children), and the SCM defines one joint distribution (Peters, Janzing and Schölkopf 2017, def. 6.2).
Words for families in a graph. If , then is a parent of and is a child of . If there is a directed path , then is an ancestor of and is a descendant of . A directed path follows the arrows; a path may go against them.
3.2 The Markov factorization #
Theorem (Markov factorization). If comes from an SCM with DAG and independent noises, then
Proof sketch.
- Order the variables so that parents come before children: .
- The chain rule of probability always holds: .
- . The earlier variables are functions of the noises only, and is independent of those noises.
- So, given , the value of depends only on , which the other earlier variables do not affect: .
- Put this into step 2.
An equivalent form is the local Markov property: each node is independent of its non-descendants given its parents.
Why this matters in numbers. With five binary variables, a general joint table has free numbers. The sprinkler factorization needs only : one conditional probability table (CPT) per node, with one row per value of its parents. The 20 numbers we save come from the independences that the missing arrows impose.
Move the sliders, or press "Random CPTs". The joint table below the graph is recomputed from the factorization. The checks on the right test five conditional independences. The three that the graph implies stay at gap 0.000 for every choice of numbers. The other two are usually dependent. Press "Make P(sprinkler) the same in both seasons": now Sprinkler and Rain are independent without conditioning, but only because of these special numbers.
3.3 The three junctions, proved #
Each statement follows from the factorization in one or two lines.
Chain . . Divide by : . So . Without conditioning, depends on in general: dependent.
Fork . . Divide by : . So . Without conditioning they are dependent in general (the common cause links them).
Collider . .
- Sum over : . So .
- Given : . The factor links and , so this does not split into a function of times a function of in general.
The collider in numbers. Take independent and with . Then , , . The partial correlation of and given is
For Gaussian variables, zero partial correlation is the same as conditional independence. So here and are dependent given , with partial correlation exactly .
A descendant of a collider. Add . Conditioning on gives partial information about , so it opens the path partly: here . Any descendant of a collider opens the path, more weakly when it is a noisier copy.
and are independent standard normal variables, and , with unit-variance noises. Which is true?
is a descendant of the collider , so conditioning on opens the path. and , so and . is a noisy copy of , so the effect is weaker than conditioning on itself.
3.4 d-separation #
Definition (blocked path; Pearl 1988). Let be a set of nodes that does not contain the two end nodes. A path between and is blocked by if it has a middle node such that
- is a non-collider on the path (chain or fork ) and ; or
- is a collider on the path (), and neither nor any descendant of is in .
A path that is not blocked is open (or active). and are d-separated by , written , if every path between them is blocked by . Otherwise they are d-connected.
Note that "collider" is a property of a node on a path, not of the node alone. In , the node is a non-collider, and is a collider. On another path, may be a collider.
Algorithm for small graphs (what the builder does).
- List every path between and (ignore arrow directions; no node twice).
- For each path, walk along its middle nodes. For each one, decide: collider or not? In ? Has it a descendant in ?
- Stop at the first node that blocks (rules 1 or 2). If no node blocks, the path is open.
- if and only if no path is open.
Section 4 runs this algorithm by hand on a five-node graph.
A linear-time algorithm: Bayes-ball (reachability)
Listing paths takes exponential time in large graphs. The Bayes-ball algorithm (Shachter 1998), also called the reachability algorithm (Koller and Friedman 2009, alg. 3.1), avoids it. It does a graph search over pairs (node, direction of arrival), "up" (arriving from a child) or "down" (arriving from a parent):
- first mark every node that is in or has a descendant in (the ancestors of , including );
- from a node reached "up", continue to its parents (up) and its children (down);
- from a node reached "down", continue to its children (down);
- from a node reached "down" that is marked (in or an ancestor of ), continue to its parents (up): this is the open collider. The nodes reached (outside ) are exactly the nodes d-connected to the start. Each (node, direction) pair is visited once, so the time is linear in the size of the graph. The builder above uses path enumeration, and we tested it against this algorithm on 200 random graphs.
3.5 What d-separation guarantees: Markov property and faithfulness #
Theorem (global Markov property; Verma and Pearl 1988, Lauritzen et al. 1990). If factorizes over , then
So d-separation is a sound rule: every independence it reads is true in every distribution that the SCM can produce, whatever the functions and noise distributions are.
The converse is not always true. A distribution is faithful to if every conditional independence in comes from a d-separation in . Two facts:
- For every DAG, the rule is complete: if and are d-connected given , there is some distribution that factorizes over in which they are dependent given (Geiger, Verma and Pearl 1990).
- Unfaithful distributions are rare: for linear-Gaussian and for discrete models, they need parameter values that satisfy an exact equation, a set of measure zero (Spirtes, Glymour and Scheines 2000; Meek 1995). They are rare, but not impossible: systems that are tuned to keep something constant (body temperature, a thermostat) can produce them.
Example of an unfaithful distribution. causes directly with weight , and through with weights and : , . Then . If , the two causal paths cancel and and are uncorrelated, although causes .
Press "Cancel the paths", then change or . The scatter shows against with the fitted line (ink) and the total effect (dashed aqua). When the line is flat: the data look as if had no effect on . Given , the partial correlation is still , so the dependence comes back. Faithfulness fails only on this thin set of weights.
Faithfulness is an assumption about the world, not a theorem. Causal discovery (lesson 10) needs it, because it reads the graph from the independences in the data.
3.6 Confounders, mediators, colliders and backdoor paths #
In causal questions we name two special nodes: a treatment and an outcome . The other nodes get names by their position:
| Name | Picture | Meaning |
|---|---|---|
| confounder | a common cause of and | |
| mediator | a node on a directed path from to ; it carries part of the effect | |
| collider | (or on any path) | a common effect on a path |
| backdoor path | a path from to whose first edge points into |
A backdoor path is never causal: it leaves against an arrow. If a backdoor path is open, part of the association between and is not the effect of . This is confounding. In the next lesson, a set that blocks every backdoor path (and has no descendant of ) lets us compute the effect.
M-bias: a pre-treatment collider. Look at the graph with unobserved (load "M-bias" in the builder). is measured before and is correlated with both and . It looks like a confounder. But it is a collider on the backdoor path. Without in the path is blocked; with in it is open. So "adjust for every pre-treatment variable" can create bias (Greenland, Pearl and Robins 1999). How large the bias is in practice is debated (Ding and Miratrix 2015); the point is that the graph, not the time order alone, decides.
In the M-bias graph (with also , and hidden), which statement about the backdoor path is correct?
is a collider on this path (). A collider blocks the path unless it (or a descendant) is in . So with no adjustment there is no bias from this path; adjusting for opens it. Time order does not decide; the position of on the path does.
3.7 Selection bias is collider bias #
Let mean "this unit is in my data". If is caused by and by (or by their causes or effects), then using only units with conditions on a collider or on its descendant. Examples:
- Berkson's paradox. Hospital patients: two diseases that are independent in the population become associated among patients, because each disease makes admission more likely.
- Survivor bias. Only the companies (or planes, or patients) that survived are in the data.
- Data filtered by an earlier model. A bank observes repayment only for applicants its model approved. Approval depends on features that also predict repayment, so a model trained on approved loans learns associations of the selected population.
- Recommender feedback. Ratings exist only for items that users chose to watch, and the choice depends on how much they expected to like the item.
The birth-weight paradox
Among low-birth-weight babies, babies of smoking mothers had lower infant mortality than babies of non-smoking mothers. Smoking does not protect babies. The explanation (Hernández-Díaz, Schisterman and Hernán 2006) is a collider: low birth weight is caused by smoking and by other, more dangerous problems (for example birth defects) that also raise mortality. Conditioning on low birth weight opens the path Smoking → Low weight ← Other problem → Death. Among low-weight babies, a non-smoking mother makes it more likely that the low weight came from a more dangerous cause.
3.8 Testable implications #
A graph implies a list of conditional independences, one for every d-separation. We can test them with data: if the data show a clear dependence where the graph says "d-separated", the graph is wrong (or the data are selected, or the sample is too small). For example, the sprinkler graph implies Sprinkler Rain Season and Slippery Season Wet, among others.
Two warnings:
- Passing the tests does not prove the graph. Different graphs can imply the same independences. The chain , the reverse chain and the fork all imply only . Only the collider differs (it implies ). Graphs with the same independences form a Markov equivalence class; lesson 10 uses this.
- A graph with no missing arrows implies no independences, so data can never reject it. Assumptions live in the missing arrows.
Answers to section 1. (1) must be heads. The bell is a collider of the two coins; once you know it rang, the coins are dependent. (2) Negative. Hiring is a collider of talent and looks; with the top 30% selected the correlation is about (see the Berkson figure). (3) No. Season → Rain → Wet is a chain; given Rain, Season and Wet are independent. (In the full sprinkler graph the answer changes: Season → Sprinkler → Wet is a second path, and Rain does not block it.)
4. Worked example #
Take five variables with the graph
We ask whether and are d-separated by different sets .
Step 1: list the paths between and . has one neighbor, . From we can go to or to .
- :
- :
There are no other paths (––– and ––– are the only routes without a repeated node).
Step 2: the type of each middle node on each path.
- On : is a collider (); is a fork ().
- On : is a chain node (); is a collider ().
The descendants are: of , only ; of , none.
Step 3: check each set .
| : (collider), (fork) | : (chain), (collider) | d-separated? | |
|---|---|---|---|
| blocked at (collider, no descendant in ) | blocked at (collider) | yes | |
| opens, passes: open | blocked at (non-collider in ) | no | |
| opens (its descendant ), passes: open | passes, opens: open | no | |
| blocked at (non-collider in ) | blocked at | yes | |
| blocked at | passes, opens: open | no | |
| open | blocked at | no |
Look at the rows and . Adding to the set closes the path that opened. This is the general recipe: when you must condition on a collider, also condition on a non-collider next to it on the same path.
Step 4: check with numbers. Make the graph a linear-Gaussian SCM with all weights 1 and unit-variance noises: , , , , . For Gaussian variables, d-separation shows up as a partial correlation of exactly 0. The exact values, from the covariance matrix, are:
The zeros are exactly the d-separated rows. For one value, by hand: with , , , , , . So , , and
5. In code #
The code simulates the worked example and computes partial correlations from the precision matrix (the inverse of the covariance matrix): for the variables , with . A d-separation function by path enumeration checks the same sets.
import numpy as np
# graph: parents of each node
parents = {"A": [], "B": [], "C": ["A", "B"], "E": ["B"], "D": ["C", "E"]}
edges = [(p, c) for c, ps in parents.items() for p in ps]
def descendants(v):
out, stack = set(), [v]
while stack:
u = stack.pop()
for p, c in edges:
if p == u and c not in out:
out.add(c)
stack.append(c)
return out
def paths(a, b, path=None):
path = path or [a]
if path[-1] == b:
yield path
return
for p, c in edges:
for u, w in ((p, c), (c, p)):
if u == path[-1] and w not in path:
yield from paths(a, b, path + [w])
def blocked(path, Z):
for u, v, w in zip(path, path[1:], path[2:]):
collider = (u, v) in edges and (w, v) in edges
if not collider and v in Z:
return True # chain or fork node in Z
if collider and v not in Z and not descendants(v) & Z:
return True # collider, not opened by Z
return False
def d_separated(a, b, Z):
return all(blocked(p, Z) for p in paths(a, b))
# simulate the linear-Gaussian SCM (all weights 1)
rng = np.random.default_rng(0)
n = 200_000
A = rng.normal(size=n); B = rng.normal(size=n)
C = A + B + rng.normal(size=n)
E = B + rng.normal(size=n)
D = C + E + rng.normal(size=n)
data = {"A": A, "B": B, "C": C, "D": D, "E": E}
def partial_corr(a, b, Z):
X = np.column_stack([data[v] for v in [a, b, *Z]])
Theta = np.linalg.inv(np.cov(X.T))
return -Theta[0, 1] / np.sqrt(Theta[0, 0] * Theta[1, 1])
for Z in [set(), {"C"}, {"D"}, {"B", "C"}, {"B", "D"}, {"C", "D"}]:
print(sorted(Z), d_separated("A", "E", Z), round(partial_corr("A", "E", sorted(Z)), 3))
# [] True -0.002
# ['C'] False -0.317
# ['D'] False -0.429
# ['B', 'C'] True -0.0
# ['B', 'D'] False -0.334
# ['C', 'D'] False -0.202
The sample values differ from the exact ones only in the third digit. In a real analysis we would test "partial correlation = 0" with a Fisher -test; the notebook does this.
6. Where it shows up #
- Choosing controls in observational studies. Epidemiology and economics draw DAGs before they choose covariates (Hernán and Robins 2020, ch. 6–8). The backdoor criterion of the next lesson is d-separation in a modified graph.
- Bayesian networks. Medical diagnosis networks, fault diagnosis and other probabilistic graphical models store a joint distribution as a product of CPTs and answer queries with the independences that d-separation gives. The Markov blanket of a node (parents, children and the other parents of its children) d-separates it from all other nodes, so it is the smallest sufficient feature set for predicting that node.
- Causal discovery. The PC algorithm removes an edge between two nodes as soon as it finds a set that makes them independent, and orients colliders from the pattern of independences (lesson 10).
- Selection bias in ML data. Loan data, click logs, filtered web text and datasets of "accepted" items are selected on a common effect. Debiasing methods (inverse propensity weighting of observed ratings, lesson 05) start from the selection graph.
- ML for science. In particle physics, a trigger keeps only events with large total energy; this selection is on a common effect of the energies of the particles, and it creates correlations between them that the analysis must model. In clinical data, only tested patients have a test result, so analyses of "tested patients" condition on the decision to test.
7. Common mistakes #
- "Conditioning on more variables can only reduce bias." False. Conditioning on a collider, or on a descendant of one, opens a path that was blocked.
- Treating "collider" as a property of a node. A node is a collider on a path. The same node can be a collider on one path and a chain node on another (node in section 4).
- Following only the arrow directions. Paths in d-separation can go against the arrows; backdoor paths always start against an arrow.
- Forgetting descendants. A collider is opened also by conditioning on any of its descendants, for example on a noisy measurement of it.
- Reading "no arrow" as "no information". A missing arrow is the strongest claim in the graph. A missing common cause (no dashed ) claims that there is no unmeasured confounding.
- Believing a graph because the data pass its tests. Markov-equivalent graphs pass the same tests, and an unfaithful distribution can hide a real effect. Check: list the graphs that imply the same independences before you claim a direction.
8. Check yourself #
- Write the Markov factorization of the graph , , , .
Answer
: one factor per node, conditioned on its parents. - In the fork , what can you say about and given ? And in the
collider ?
Answer
Fork: independent given (conditioning on the common cause blocks the path). Collider: dependent given in general (conditioning on the common effect opens the path), although and are independent without conditioning. - Graph: , , . Which paths between and are
open when ? When ?
Answer
Paths: (always open: no middle node), , . With : open, the backdoor path blocked at , the path through blocked (collider not in ). With : the path through also opens, because is a collider in . - Give two different graphs on that imply exactly the same independences, and one that
implies a different one.
Answer
and both imply only . The collider implies (without conditioning) instead. - A dataset contains only employees who are still at the company after two years. Training
satisfaction and salary both make people stay. Why can satisfaction and salary look negatively
associated in this data even if they are independent among all hires?
Answer
Staying is a common effect: satisfaction → stays ← salary. The dataset keeps only units with "stays = 1", which conditions on a collider. Among the people who stayed, a low salary must be balanced by high satisfaction more often (Berkson's paradox). - Why can a faithful-looking dataset still come from a graph with an arrow , although
and are uncorrelated?
Answer
Two causal paths can cancel exactly, for example a direct effect and an indirect effect with . Then the distribution is not faithful: it has an independence that the graph does not imply.
What is a structural causal model?
A set of assignments , one per variable, with jointly independent noises . Its causal graph has an arrow from each parent to its child.
What does a missing arrow claim in a causal graph?
That has no direct effect on : is not an input of the assignment of .
Write the Markov factorization of a DAG.
.
In a chain , are and independent given ?
Yes. , so conditioning on the middle node blocks the chain.
In a collider , what happens when you condition on ?
and , independent before, become dependent: knowing the common effect links its causes ("explaining away").
independent, with unit noise. What is the partial correlation of and given ?
.
When is a path blocked by a set ?
When it has a non-collider in , or a collider such that neither it nor any of its descendants is in .
What is faithfulness, and when does it fail?
Every conditional independence in the distribution comes from a d-separation in the graph. It fails when effects cancel exactly, for example a direct effect .
What is a backdoor path from to ?
A path between and whose first edge points into . It is never causal; if open, it creates confounding.
Why is Berkson's paradox a case of collider bias?
The data keep only units selected by a common effect of two variables, which conditions on a collider and makes the two variables dependent in the selected data.
9. Interview angle #
- "What is a collider and why should you not control for it?" A common effect of two variables on a path. Controlling for it (or for one of its descendants) opens the path and creates an association between its causes. Example: controlling for "reviews" when estimating the effect of ads on sales, if both ads and sales cause reviews.
- "Explain Berkson's paradox with a hiring example." Hiring depends on skill + charisma. Among hired people, the two are negatively correlated, although they are independent among applicants: a low-skill hire must have high charisma to be hired.
- "Draw the graph of a recommender's feedback loop." Past recommendations → items shown → clicks; user preference → clicks; clicks → next model → next recommendations. Training on logged clicks conditions on "item was shown", which depends on the old model; the logged data are a selected sample.
- "Can data tell you the direction of an arrow?" Not from independences alone between two variables. With three variables, a collider has a different pattern of independences than a chain or fork, so some directions can be found (lesson 10).
10. Practice #
The notebook for this lesson has five exercises, each with a check cell and a folded solution:
sample_scm(parents, coefs, n, seed): sample from a linear-Gaussian SCM given as a dictionary of parents.d_separated(parents, a, b, Z): d-separation by path enumeration; checked on the presets of this lesson and against the independences found in large samples of random graphs.partial_corrfrom the inverse covariance matrix and a Fisher -test; check the collider value .- Berkson's paradox: simulate talent and looks, select the top share, and plot the correlation inside the selection against the share.
- List all conditional independences that the sprinkler graph implies (pairs with sets of size up to 2) and test them on a binary sample of 20,000 units.
11. Watch and read #
- Judea Pearl, Madelyn Glymour and Nicholas Jewell, Causal Inference in Statistics: A Primer (Wiley 2016), ch. 2; companion site with solutions.
- Brady Neal, Introduction to Causal Inference, free course notes, ch. 3 (graphs, d-separation).
- Jonas Peters, Dominik Janzing and Bernhard Schölkopf, Elements of Causal Inference (MIT Press 2017, open access), ch. 6.1–6.5 (SCMs, Markov property, faithfulness).
- Miguel Hernán and James Robins, Causal Inference: What If, free book, ch. 6 (graphs) and ch. 8 (selection bias).
- Daphne Koller and Nir Friedman, Probabilistic Graphical Models (MIT Press 2009), ch. 3 (d-separation, Bayes-ball).
Runs in your browser with nothing to install. The notebook repeats the key demos and has exercises with checks; solutions are inside, folded.
No Google account needed: Python runs inside your browser. The first start downloads about 40 MB; your changes are saved in this browser only.