{
 "nbformat": 4,
 "nbformat_minor": 5,
 "metadata": {
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3"
  },
  "language_info": {
   "name": "python"
  },
  "colab": {
   "provenance": []
  }
 },
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Day 46: Graph Basics & Directed Acyclic Graphs\n",
    "\n",
    "*Beginner to Ninja: Math for Causal Inference*\n",
    "\n",
    "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.\n",
    "\n",
    "**Learning outcomes**\n",
    "\n",
    "- Read off parents, children, ancestors and descendants from a DAG.\n",
    "- Decide whether a directed graph is acyclic and find a topological order.\n",
    "- Use powers of the adjacency matrix to count directed paths.\n",
    "- Translate an economic or health story into a causal DAG and list the assumptions it encodes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Theory\n",
    "\n",
    "### Why graphs?\n",
    "\n",
    "Phase 3 asked *whether* treatment is as good as random given $X$ (unconfoundedness). It never said **which** $X$. 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.\n",
    "\n",
    "### Directed graphs\n",
    "\n",
    "A **directed graph** $G = (V, E)$ has a set of **nodes** $V$ (variables) and a set of **directed edges** $E$, written $A \\to B$.\n",
    "\n",
    "- If $A \\to B$, then $A$ is a **parent** of $B$ and $B$ is a **child** of $A$. We write $\\operatorname{pa}(B)$ for the parents of $B$.\n",
    "- A **path** is a sequence of distinct nodes, each adjacent to the next, *ignoring arrow directions*: $A \\to B \\leftarrow C$ is a path from $A$ to $C$.\n",
    "- A **directed (causal) path** follows the arrows: $A \\to B \\to C$.\n",
    "- $A$ is an **ancestor** of $B$ if there is a directed path from $A$ to $B$; then $B$ is a **descendant** of $A$. By convention every node is its own ancestor and descendant.\n",
    "- A **cycle** is a directed path from a node back to itself: $A \\to B \\to C \\to A$.\n",
    "\n",
    "### DAGs\n",
    "\n",
    "A **directed acyclic graph (DAG)** has no directed cycles. Equivalent characterisation:\n",
    "\n",
    "$$\n",
    "G \\text{ is acyclic} \\iff \\text{its nodes can be ordered } V_1, \\dots, V_p \\text{ so that every edge points forward } (V_i \\to V_j \\Rightarrow i < j).\n",
    "$$\n",
    "\n",
    "Such an ordering is a **topological order**. In a causal DAG it is a possible *time order*: causes before effects.\n",
    "\n",
    "### Causal DAGs\n",
    "\n",
    "In a **causal DAG**, an arrow $A \\to B$ means \"$A$ is a direct cause of $B$ relative to the variables in the graph\". Two rules:\n",
    "\n",
    "1. **Missing arrows are the strong assumptions.** Omitting $A \\to B$ claims that $A$ has no direct effect on $B$. Drawing an arrow only allows an effect; it may be zero.\n",
    "2. **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 $U$.\n",
    "\n",
    "Example (returns to schooling): ability $A$ affects schooling $S$ and wages $W$; family background $F$ affects $A$ and $S$; schooling affects wages directly and through occupation $O$:\n",
    "\n",
    "$$\n",
    "F \\to A, \\quad F \\to S, \\quad A \\to S, \\quad A \\to W, \\quad S \\to O, \\quad S \\to W, \\quad O \\to W.\n",
    "$$\n",
    "\n",
    "### The adjacency matrix\n",
    "\n",
    "Number the nodes $1, \\dots, p$. The **adjacency matrix** $A$ has $A_{ij} = 1$ if $i \\to j$, else 0. Then\n",
    "\n",
    "$$\n",
    "(A^k)_{ij} = \\text{number of directed paths (walks) of length } k \\text{ from } i \\text{ to } j.\n",
    "$$\n",
    "\n",
    "A graph is acyclic exactly when $A$ is **nilpotent**: $A^p = 0$. In a topological order, $A$ is strictly upper triangular.\n",
    "\n",
    "### Time and feedback\n",
    "\n",
    "Real systems have feedback (health ↔ income). A DAG handles it by **indexing time**: $\\text{Health}_{t} \\to \\text{Income}_{t+1} \\to \\text{Health}_{t+2}$ is acyclic even though the two variables affect each other.\n",
    "\n",
    "## Part 1 · Warm-up\n",
    "\n",
    "Use this graph for Exercises 1–3: $A \\to B$, $A \\to C$, $B \\to C$, $B \\to D$, $C \\to D$, $E \\to D$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 1 · Parents and children\n",
    "\n",
    "List the parents and children of each node.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Read each arrow $X \\to Y$ as \"X is a parent of Y\".\n",
    "\n",
    "**Step 2.** Parents: $\\operatorname{pa}(A) = \\varnothing$, $\\operatorname{pa}(B) = \\{A\\}$, $\\operatorname{pa}(C) = \\{A, B\\}$, $\\operatorname{pa}(D) = \\{B, C, E\\}$, $\\operatorname{pa}(E) = \\varnothing$.\n",
    "\n",
    "**Step 3.** Children: $\\operatorname{ch}(A) = \\{B, C\\}$, $\\operatorname{ch}(B) = \\{C, D\\}$, $\\operatorname{ch}(C) = \\{D\\}$, $\\operatorname{ch}(D) = \\varnothing$, $\\operatorname{ch}(E) = \\{D\\}$.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 2 · Ancestors and descendants\n",
    "\n",
    "Find the ancestors of $D$ and the descendants of $B$.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Ancestors of $D$: its parents $B, C, E$, their parents $A$ (of $B$ and $C$), and $D$ itself.\n",
    "\n",
    "**Step 2.** So $\\operatorname{an}(D) = \\{A, B, C, D, E\\}$.\n",
    "\n",
    "**Step 3.** Descendants of $B$: $B \\to C$, $B \\to D$, $C \\to D$.\n",
    "\n",
    "**Answer:** $\\operatorname{an}(D) = \\{A, B, C, D, E\\}$ and $\\operatorname{de}(B) = \\{B, C, D\\}$.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 3 · A topological order\n",
    "\n",
    "Give two different topological orders.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Sources (no parents): $A$ and $E$. Either can go first.\n",
    "\n",
    "**Step 2.** After $A$: $B$ (its only parent is placed), then $C$ (parents $A, B$ placed).\n",
    "\n",
    "**Step 3.** $D$ needs $B, C, E$ placed, so it is last.\n",
    "\n",
    "**Answer:** $A, B, C, E, D$ and $E, A, B, C, D$ (also $A, E, B, C, D$, and others).\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 4 · Counting paths with matrices\n",
    "\n",
    "For $A \\to B$, $A \\to C$, $B \\to C$, $B \\to D$, $C \\to D$ (order $A, B, C, D$), compute $A^2$ and $A^3$ and count the directed paths from $A$ to $D$.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** The adjacency matrix is\n",
    "\n",
    "$$\n",
    "\\mathbf A = \\begin{pmatrix} 0 & 1 & 1 & 0 \\\\ 0 & 0 & 1 & 1 \\\\ 0 & 0 & 0 & 1 \\\\ 0 & 0 & 0 & 0 \\end{pmatrix}.\n",
    "$$\n",
    "\n",
    "**Step 2.** Row $A$ of $\\mathbf A^2$: $(\\mathbf A^2)_{AD} = \\sum_k \\mathbf A_{Ak}\\mathbf A_{kD} = \\mathbf A_{AB}\\mathbf A_{BD} + \\mathbf A_{AC}\\mathbf A_{CD} = 1 + 1 = 2$ (paths $A \\to B \\to D$ and $A \\to C \\to D$).\n",
    "\n",
    "**Step 3.** Also $(\\mathbf A^2)_{AC} = \\mathbf A_{AB}\\mathbf A_{BC} = 1$ and $(\\mathbf A^2)_{BD} = \\mathbf A_{BC}\\mathbf A_{CD} = 1$; every other entry is 0.\n",
    "\n",
    "**Step 4.** $(\\mathbf A^3)_{AD} = \\sum_k (\\mathbf A^2)_{Ak}\\mathbf A_{kD} = (\\mathbf A^2)_{AC}\\mathbf A_{CD} = 1$ (path $A \\to B \\to C \\to D$). All other entries of $\\mathbf A^3$ are 0, and $\\mathbf A^4 = 0$.\n",
    "\n",
    "**Answer:** $2 + 1 = 3$ directed paths from $A$ to $D$.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 5 · Spot the cycle\n",
    "\n",
    "Is the graph $X \\to Y$, $Y \\to Z$, $Z \\to W$, $W \\to Y$ a DAG? If not, give the cycle and fix it by adding time indices.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Follow the arrows from $Y$: $Y \\to Z \\to W \\to Y$. That is a directed cycle.\n",
    "\n",
    "**Step 2.** So the graph is not a DAG and has no topological order.\n",
    "\n",
    "**Step 3.** With time indices the loop unrolls: $Y_t \\to Z_t \\to W_t \\to Y_{t+1} \\to Z_{t+1} \\to \\dots$, with $X \\to Y_t$ at each period.\n",
    "\n",
    "**Answer:** not a DAG; the time-indexed graph is.\n",
    "\n",
    "</details>\n",
    "\n",
    "## Part 2 · Proofs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 6 · Every DAG has a source\n",
    "\n",
    "Prove that every finite DAG has at least one node with no parents.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Suppose not: every node has a parent.\n",
    "\n",
    "**Step 2.** Start at any node $V_0$ and move to a parent $V_1$, then to a parent of $V_1$, and so on. This walk never stops, since every node has a parent.\n",
    "\n",
    "**Step 3.** With $p$ nodes, after $p$ steps some node must repeat: $V_i = V_j$ with $i < j$.\n",
    "\n",
    "**Step 4.** Reversed, the walk $V_j \\to V_{j-1} \\to \\dots \\to V_i$ is a directed path from $V_i$ back to itself: a cycle.\n",
    "\n",
    "**Step 5.** That contradicts acyclicity, so a node with no parents exists. $\\blacksquare$\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 7 · DAGs have topological orders\n",
    "\n",
    "Prove by induction on the number of nodes that every DAG has a topological order.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Base case: one node, no edges; the single node is an order.\n",
    "\n",
    "**Step 2.** Induction step: let $G$ have $p$ nodes. By Exercise 6 it has a source $S$.\n",
    "\n",
    "**Step 3.** Delete $S$ and its edges. The remaining graph has $p - 1$ nodes and is still acyclic (deleting edges cannot create cycles).\n",
    "\n",
    "**Step 4.** By the induction hypothesis it has a topological order $V_1, \\dots, V_{p-1}$.\n",
    "\n",
    "**Step 5.** Put $S$ first: $S, V_1, \\dots, V_{p-1}$. Edges out of $S$ point forward because $S$ is first; no edge points into $S$; all other edges point forward by the hypothesis. $\\blacksquare$\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 8 · Orders imply acyclicity\n",
    "\n",
    "Prove the converse: if a topological order exists, the graph has no directed cycle.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Let $\\pi(V)$ be the position of $V$ in the order. Every edge $V \\to W$ has $\\pi(V) < \\pi(W)$.\n",
    "\n",
    "**Step 2.** Along a directed path $V_0 \\to V_1 \\to \\dots \\to V_k$, positions strictly increase: $\\pi(V_0) < \\pi(V_1) < \\dots < \\pi(V_k)$.\n",
    "\n",
    "**Step 3.** A cycle would need $V_k = V_0$, so $\\pi(V_0) < \\pi(V_0)$: impossible. $\\blacksquare$\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 9 · Matrix powers count paths\n",
    "\n",
    "Prove by induction that $(\\mathbf A^k)_{ij}$ equals the number of directed walks of length $k$ from $i$ to $j$.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Base case $k = 1$: $\\mathbf A_{ij} = 1$ exactly when the one-edge walk $i \\to j$ exists.\n",
    "\n",
    "**Step 2.** Assume the claim for $k$. A walk of length $k + 1$ from $i$ to $j$ is a walk of length $k$ from $i$ to some node $m$, followed by the edge $m \\to j$.\n",
    "\n",
    "**Step 3.** Summing over the last intermediate node $m$:\n",
    "\n",
    "$$\n",
    "\\#\\{\\text{walks of length } k + 1\\} = \\sum_m (\\mathbf A^k)_{im}\\,\\mathbf A_{mj} = (\\mathbf A^{k+1})_{ij}.\n",
    "$$\n",
    "\n",
    "**Step 4.** In a DAG every walk is a path (a repeated node would create a cycle), so the entries count directed paths. $\\blacksquare$\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 10 · Acyclic means nilpotent\n",
    "\n",
    "Prove that a graph with $p$ nodes is acyclic if and only if $\\mathbf A^p = 0$.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** (⇒) In a DAG a directed path visits distinct nodes, so it has at most $p - 1$ edges.\n",
    "\n",
    "**Step 2.** Hence there are no walks of length $p$, and by Exercise 9 every entry of $\\mathbf A^p$ is 0.\n",
    "\n",
    "**Step 3.** (⇐) If there is a cycle through node $i$ of length $c \\le p$, then going around it repeatedly gives walks from $i$ to $i$ of every length that is a multiple of $c$.\n",
    "\n",
    "**Step 4.** Going around and then stopping partway gives walks of length exactly $p$ from $i$ to some node, so $\\mathbf A^p \\ne 0$. $\\blacksquare$\n",
    "\n",
    "</details>\n",
    "\n",
    "## Part 3 · Applied"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 11 · Returns to schooling\n",
    "\n",
    "Use the schooling DAG from the theory: $F \\to A$, $F \\to S$, $A \\to S$, $A \\to W$, $S \\to O$, $S \\to W$, $O \\to W$ (family background, ability, schooling, occupation, wage). (a) List all directed paths from $S$ to $W$. (b) List all other paths from $S$ to $W$. (c) Which variables are descendants of schooling?\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** (a) Directed paths: $S \\to W$ and $S \\to O \\to W$. These carry the causal effect of schooling.\n",
    "\n",
    "**Step 2.** (b) Paths that start with an arrow *into* $S$: $S \\leftarrow A \\to W$ and $S \\leftarrow F \\to A \\to W$.\n",
    "\n",
    "**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).\n",
    "\n",
    "**Step 4.** (c) Descendants of $S$: $S$, $O$, $W$.\n",
    "\n",
    "**Answer:** occupation is a descendant (a mediator), so controlling for it would remove part of the effect of schooling (Day 53).\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 12 · Assumptions hidden in missing arrows\n",
    "\n",
    "A cardiology team draws: age $\\to$ obesity, age $\\to$ heart disease, obesity $\\to$ diabetes, diabetes $\\to$ heart disease, smoking $\\to$ heart disease. List three missing arrows and the substantive claim each makes. Which is the least plausible?\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** No arrow obesity $\\to$ heart disease: obesity affects heart disease *only* through diabetes.\n",
    "\n",
    "**Step 2.** No arrow age $\\to$ diabetes: age affects diabetes only through obesity.\n",
    "\n",
    "**Step 3.** No arrow between age and smoking, and no common cause of them: smoking is unrelated to age (and to income, education...).\n",
    "\n",
    "**Step 4.** The obesity claim is the least plausible: obesity raises blood pressure and cholesterol, which affect heart disease without diabetes.\n",
    "\n",
    "**Answer:** every missing arrow is a testable or debatable claim; arguing about the graph means arguing about these claims.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 13 · Insurance and health spending\n",
    "\n",
    "An analyst writes: health insurance $\\to$ health spending $\\to$ health $\\to$ insurance (sicker people buy coverage). Explain the problem and redraw the graph for annual panel data.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Insurance $\\to$ spending $\\to$ health $\\to$ insurance is a directed cycle, so it is not a DAG and the causal question \"effect of insurance\" is ill-posed in this graph.\n",
    "\n",
    "**Step 2.** In reality the arrows act at different times: this year's health affects next year's insurance choice.\n",
    "\n",
    "**Step 3.** Time-indexed DAG:\n",
    "\n",
    "$$\n",
    "\\text{Health}_{t-1} \\to \\text{Ins}_t \\to \\text{Spend}_t \\to \\text{Health}_t \\to \\text{Ins}_{t+1}, \\quad \\text{Health}_{t-1} \\to \\text{Health}_t.\n",
    "$$\n",
    "\n",
    "**Answer:** the unrolled graph is acyclic, and it shows that $\\text{Health}_{t-1}$ is a common cause of insurance and later health, which must be accounted for.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 14 · Paths from the minimum wage to poverty\n",
    "\n",
    "A labour economist draws: minimum wage $W$ $\\to$ employment $E$, $W \\to$ hours $H$, $W \\to$ earnings $R$, $E \\to R$, $H \\to R$, $R \\to$ poverty $P$, $E \\to P$. Count the directed paths from $W$ to $P$ by length using the adjacency matrix, and list them.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Order the nodes $W, E, H, R, P$. Arrows out of each: $W: E, H, R$; $E: R, P$; $H: R$; $R: P$.\n",
    "\n",
    "**Step 2.** Length 2: $(\\mathbf A^2)_{WP} = \\sum_k \\mathbf A_{Wk}\\mathbf A_{kP} = \\mathbf A_{WE}\\mathbf A_{EP} + \\mathbf A_{WR}\\mathbf A_{RP} = 2$: paths $W \\to E \\to P$ and $W \\to R \\to P$.\n",
    "\n",
    "**Step 3.** Length 3: $(\\mathbf A^2)_{WR} = \\mathbf A_{WE}\\mathbf A_{ER} + \\mathbf A_{WH}\\mathbf A_{HR} = 2$, so $(\\mathbf A^3)_{WP} = (\\mathbf A^2)_{WR}\\,\\mathbf A_{RP} = 2$: paths $W \\to E \\to R \\to P$ and $W \\to H \\to R \\to P$.\n",
    "\n",
    "**Step 4.** Length 4: $(\\mathbf A^4)_{WP} = 0$, since no path from $W$ has four edges.\n",
    "\n",
    "**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.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 15 · Pre-treatment variables\n",
    "\n",
    "In a job-training evaluation the DAG is: age $\\to$ training $T$, age $\\to$ earnings $Y$, prior earnings $P \\to T$, $P \\to Y$, $T \\to$ certificate $C \\to Y$, $T \\to Y$, $T \\to$ job-search effort $J$, $Y \\to$ savings $S$. Which variables are descendants of $T$, and which are not?\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Follow arrows out of $T$: $T \\to C$, $T \\to J$, $T \\to Y$, and then $C \\to Y$, $Y \\to S$.\n",
    "\n",
    "**Step 2.** Descendants: $\\{T, C, J, Y, S\\}$.\n",
    "\n",
    "**Step 3.** Non-descendants: age and $P$.\n",
    "\n",
    "**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.\n",
    "\n",
    "</details>\n",
    "\n",
    "## Verify in Python"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import networkx as nx\n",
    "\n",
    "G = nx.DiGraph([(\"A\", \"B\"), (\"A\", \"C\"), (\"B\", \"C\"), (\"B\", \"D\"), (\"C\", \"D\"), (\"E\", \"D\")])\n",
    "print(\"acyclic:\", nx.is_directed_acyclic_graph(G))\n",
    "print(\"parents of D:\", sorted(G.predecessors(\"D\")), \" ancestors of D:\", sorted(nx.ancestors(G, \"D\")))\n",
    "print(\"descendants of B:\", sorted(nx.descendants(G, \"B\")))\n",
    "print(\"all topological orders:\", [\"\".join(o) for o in nx.all_topological_sorts(G)])\n",
    "\n",
    "# Exercise 4: counting paths with matrix powers\n",
    "H = nx.DiGraph([(\"A\", \"B\"), (\"A\", \"C\"), (\"B\", \"C\"), (\"B\", \"D\"), (\"C\", \"D\")])\n",
    "nodes = [\"A\", \"B\", \"C\", \"D\"]\n",
    "M = nx.to_numpy_array(H, nodelist=nodes, dtype=int)\n",
    "for k in (1, 2, 3, 4):\n",
    "    print(f\"A^{k}[A,D] =\", np.linalg.matrix_power(M, k)[0, 3])\n",
    "\n",
    "# Exercise 14: minimum wage -> poverty\n",
    "W = nx.DiGraph([(\"W\", \"E\"), (\"W\", \"H\"), (\"W\", \"R\"), (\"E\", \"R\"), (\"H\", \"R\"), (\"R\", \"P\"), (\"E\", \"P\")])\n",
    "print([\" -> \".join(p) for p in nx.all_simple_paths(W, \"W\", \"P\")])\n",
    "M = nx.to_numpy_array(W, nodelist=list(\"WEHRP\"), dtype=int)\n",
    "print(\"paths by length:\", [np.linalg.matrix_power(M, k)[0, 4] for k in (1, 2, 3, 4)])\n",
    "print(\"nilpotent (A^5 = 0):\", not np.linalg.matrix_power(M, 5).any())\n",
    "\n",
    "# Exercise 5: a cycle\n",
    "C = nx.DiGraph([(\"X\", \"Y\"), (\"Y\", \"Z\"), (\"Z\", \"W\"), (\"W\", \"Y\")])\n",
    "print(\"cycle:\", nx.find_cycle(C))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Key takeaways\n",
    "\n",
    "- 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.\n",
    "- Acyclic ⇔ topological order exists ⇔ the adjacency matrix is nilpotent. Feedback is handled by indexing time.\n",
    "- Directed paths carry causal effects; other paths (through common causes) carry non-causal association. The next days make this precise."
   ]
  }
 ]
}