Day 46. Graph Basics & Directed Acyclic Graphs
The vocabulary of causal graphs (nodes, arrows, paths, parents, ancestors, descendants), acyclicity and topological orders, and what an arrow, and a missing arrow, claims about the world.
- ●Read off parents, children, ancestors and descendants from a DAG.
- ●Decide whether a directed graph is acyclic and find a topological order.
- ●Use powers of the adjacency matrix to count directed paths.
- ●Translate an economic or health story into a causal DAG and list the assumptions it encodes.
Theory
Why graphs?
Phase 3 asked whether treatment is as good as random given (unconfoundedness). It never said which . Causal graphs answer that question by drawing the assumptions about how variables cause each other, then reading off which comparisons are valid. Phase 4 builds that machinery.
Directed graphs
A directed graph has a set of nodes (variables) and a set of directed edges , written .
- If , then is a parent of and is a child of . We write for the parents of .
- A path is a sequence of distinct nodes, each adjacent to the next, ignoring arrow directions: is a path from to .
- A directed (causal) path follows the arrows: .
- is an ancestor of if there is a directed path from to ; then is a descendant of . By convention every node is its own ancestor and descendant.
- A cycle is a directed path from a node back to itself: .
DAGs
A directed acyclic graph (DAG) has no directed cycles. Equivalent characterisation:
Such an ordering is a topological order. In a causal DAG it is a possible time order: causes before effects.
Causal DAGs
In a causal DAG, an arrow means " is a direct cause of relative to the variables in the graph". Two rules:
- Missing arrows are the strong assumptions. Omitting claims that has no direct effect on . Drawing an arrow only allows an effect; it may be zero.
- Common causes must be included. If any variable (observed or not) causes two variables in the graph, it must appear, perhaps as an unobserved node .
Example (returns to schooling): ability affects schooling and wages ; family background affects and ; schooling affects wages directly and through occupation :
The adjacency matrix
Number the nodes . The adjacency matrix has if , else 0. Then
A graph is acyclic exactly when is nilpotent: . In a topological order, is strictly upper triangular.
Time and feedback
Real systems have feedback (health ↔ income). A DAG handles it by indexing time: is acyclic even though the two variables affect each other.
Part 1 · Warm-up
Use this graph for Exercises 1–3: , , , , , .
Exercise 1 · Parents and children
List the parents and children of each node.
Show solution
Step 1. Read each arrow as "X is a parent of Y".
Step 2. Parents: , , , , .
Step 3. Children: , , , , .
Exercise 2 · Ancestors and descendants
Find the ancestors of and the descendants of .
Show solution
Step 1. Ancestors of : its parents , their parents (of and ), and itself.
Step 2. So .
Step 3. Descendants of : , , .
Answer: and .
Exercise 3 · A topological order
Give two different topological orders.
Show solution
Step 1. Sources (no parents): and . Either can go first.
Step 2. After : (its only parent is placed), then (parents placed).
Step 3. needs placed, so it is last.
Answer: and (also , and others).
Exercise 4 · Counting paths with matrices
For , , , , (order ), compute and and count the directed paths from to .
Show solution
Step 1. The adjacency matrix is
Step 2. Row of : (paths and ).
Step 3. Also and ; every other entry is 0.
Step 4. (path ). All other entries of are 0, and .
Answer: directed paths from to .
Exercise 5 · Spot the cycle
Is the graph , , , a DAG? If not, give the cycle and fix it by adding time indices.
Show solution
Step 1. Follow the arrows from : . That is a directed cycle.
Step 2. So the graph is not a DAG and has no topological order.
Step 3. With time indices the loop unrolls: , with at each period.
Answer: not a DAG; the time-indexed graph is.
Part 2 · Proofs
Exercise 6 · Every DAG has a source
Prove that every finite DAG has at least one node with no parents.
Show solution
Step 1. Suppose not: every node has a parent.
Step 2. Start at any node and move to a parent , then to a parent of , and so on. This walk never stops, since every node has a parent.
Step 3. With nodes, after steps some node must repeat: with .
Step 4. Reversed, the walk is a directed path from back to itself: a cycle.
Step 5. That contradicts acyclicity, so a node with no parents exists.
Exercise 7 · DAGs have topological orders
Prove by induction on the number of nodes that every DAG has a topological order.
Show solution
Step 1. Base case: one node, no edges; the single node is an order.
Step 2. Induction step: let have nodes. By Exercise 6 it has a source .
Step 3. Delete and its edges. The remaining graph has nodes and is still acyclic (deleting edges cannot create cycles).
Step 4. By the induction hypothesis it has a topological order .
Step 5. Put first: . Edges out of point forward because is first; no edge points into ; all other edges point forward by the hypothesis.
Exercise 8 · Orders imply acyclicity
Prove the converse: if a topological order exists, the graph has no directed cycle.
Show solution
Step 1. Let be the position of in the order. Every edge has .
Step 2. Along a directed path , positions strictly increase: .
Step 3. A cycle would need , so : impossible.
Exercise 9 · Matrix powers count paths
Prove by induction that equals the number of directed walks of length from to .
Show solution
Step 1. Base case : exactly when the one-edge walk exists.
Step 2. Assume the claim for . A walk of length from to is a walk of length from to some node , followed by the edge .
Step 3. Summing over the last intermediate node :
Step 4. In a DAG every walk is a path (a repeated node would create a cycle), so the entries count directed paths.
Exercise 10 · Acyclic means nilpotent
Prove that a graph with nodes is acyclic if and only if .
Show solution
Step 1. (⇒) In a DAG a directed path visits distinct nodes, so it has at most edges.
Step 2. Hence there are no walks of length , and by Exercise 9 every entry of is 0.
Step 3. (⇐) If there is a cycle through node of length , then going around it repeatedly gives walks from to of every length that is a multiple of .
Step 4. Going around and then stopping partway gives walks of length exactly from to some node, so .
Part 3 · Applied
Exercise 11 · Returns to schooling
Use the schooling DAG from the theory: , , , , , , (family background, ability, schooling, occupation, wage). (a) List all directed paths from to . (b) List all other paths from to . (c) Which variables are descendants of schooling?
Show solution
Step 1. (a) Directed paths: and . These carry the causal effect of schooling.
Step 2. (b) Paths that start with an arrow into : and .
Step 3. Both run through ability, a common cause of schooling and wages; they generate correlation that is not causal (Day 52 calls them back-door paths).
Step 4. (c) Descendants of : , , .
Answer: occupation is a descendant (a mediator), so controlling for it would remove part of the effect of schooling (Day 53).
Exercise 12 · Assumptions hidden in missing arrows
A cardiology team draws: age obesity, age heart disease, obesity diabetes, diabetes heart disease, smoking heart disease. List three missing arrows and the substantive claim each makes. Which is the least plausible?
Show solution
Step 1. No arrow obesity heart disease: obesity affects heart disease only through diabetes.
Step 2. No arrow age diabetes: age affects diabetes only through obesity.
Step 3. No arrow between age and smoking, and no common cause of them: smoking is unrelated to age (and to income, education...).
Step 4. The obesity claim is the least plausible: obesity raises blood pressure and cholesterol, which affect heart disease without diabetes.
Answer: every missing arrow is a testable or debatable claim; arguing about the graph means arguing about these claims.
Exercise 13 · Insurance and health spending
An analyst writes: health insurance health spending health insurance (sicker people buy coverage). Explain the problem and redraw the graph for annual panel data.
Show solution
Step 1. Insurance spending health insurance is a directed cycle, so it is not a DAG and the causal question "effect of insurance" is ill-posed in this graph.
Step 2. In reality the arrows act at different times: this year's health affects next year's insurance choice.
Step 3. Time-indexed DAG:
Answer: the unrolled graph is acyclic, and it shows that is a common cause of insurance and later health, which must be accounted for.
Exercise 14 · Paths from the minimum wage to poverty
A labour economist draws: minimum wage employment , hours , earnings , , , poverty , . Count the directed paths from to by length using the adjacency matrix, and list them.
Show solution
Step 1. Order the nodes . Arrows out of each: ; ; ; .
Step 2. Length 2: : paths and .
Step 3. Length 3: , so : paths and .
Step 4. Length 4: , since no path from has four edges.
Answer: 4 causal channels. The total effect of the minimum wage on poverty adds them up; a positive hourly wage effect can be offset by negative employment and hours effects.
Exercise 15 · Pre-treatment variables
In a job-training evaluation the DAG is: age training , age earnings , prior earnings , , certificate , , job-search effort , savings . Which variables are descendants of , and which are not?
Show solution
Step 1. Follow arrows out of : , , , and then , .
Step 2. Descendants: .
Step 3. Non-descendants: age and .
Answer: age and prior earnings are pre-treatment variables, the natural candidates for adjustment. Certificate, job-search effort and savings are affected by training; Day 53 shows why adjusting for them is a mistake.
Verify in Python
import numpy as np
import networkx as nx
G = nx.DiGraph([("A", "B"), ("A", "C"), ("B", "C"), ("B", "D"), ("C", "D"), ("E", "D")])
print("acyclic:", nx.is_directed_acyclic_graph(G))
print("parents of D:", sorted(G.predecessors("D")), " ancestors of D:", sorted(nx.ancestors(G, "D")))
print("descendants of B:", sorted(nx.descendants(G, "B")))
print("all topological orders:", ["".join(o) for o in nx.all_topological_sorts(G)])
# Exercise 4: counting paths with matrix powers
H = nx.DiGraph([("A", "B"), ("A", "C"), ("B", "C"), ("B", "D"), ("C", "D")])
nodes = ["A", "B", "C", "D"]
M = nx.to_numpy_array(H, nodelist=nodes, dtype=int)
for k in (1, 2, 3, 4):
print(f"A^{k}[A,D] =", np.linalg.matrix_power(M, k)[0, 3])
# Exercise 14: minimum wage -> poverty
W = nx.DiGraph([("W", "E"), ("W", "H"), ("W", "R"), ("E", "R"), ("H", "R"), ("R", "P"), ("E", "P")])
print([" -> ".join(p) for p in nx.all_simple_paths(W, "W", "P")])
M = nx.to_numpy_array(W, nodelist=list("WEHRP"), dtype=int)
print("paths by length:", [np.linalg.matrix_power(M, k)[0, 4] for k in (1, 2, 3, 4)])
print("nilpotent (A^5 = 0):", not np.linalg.matrix_power(M, 5).any())
# Exercise 5: a cycle
C = nx.DiGraph([("X", "Y"), ("Y", "Z"), ("Z", "W"), ("W", "Y")])
print("cycle:", nx.find_cycle(C))
Key takeaways
- A causal DAG is a set of claims: each arrow allows a direct effect, each missing arrow rules one out, and every common cause must be drawn.
- Acyclic ⇔ topological order exists ⇔ the adjacency matrix is nilpotent. Feedback is handled by indexing time.
- Directed paths carry causal effects; other paths (through common causes) carry non-causal association. The next days make this precise.
Simulation Lab: Build a Causal DAG
Open full screen →Click to add arrows to a returns-to-schooling graph; see parents, ancestors, cycles, topological orders and path counts.
| F | A | S | O | W | R | |
|---|---|---|---|---|---|---|
| F | 0 | 1 | 1 | 0 | 0 | 0 |
| A | 0 | 0 | 1 | 0 | 1 | 0 |
| S | 0 | 0 | 0 | 1 | 1 | 0 |
| O | 0 | 0 | 0 | 0 | 1 | 0 |
| W | 0 | 0 | 0 | 0 | 0 | 0 |
| R | 0 | 0 | 0 | 0 | 0 | 0 |
Click one node, then another, to add or remove the arrow between them (click the same node twice to cancel). The panel reports parents, children, ancestors and descendants of the last node clicked, checks acyclicity and counts the causal paths from schooling to wages.