{
 "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 54: The Front-Door Criterion\n",
    "\n",
    "*Beginner to Ninja: Math for Causal Inference*\n",
    "\n",
    "When the confounder of treatment and outcome is unobserved, a fully mediating, unconfounded mechanism can still identify the effect. Estimate how the treatment moves the mediator, then how the mediator moves the outcome, and chain the two.\n",
    "\n",
    "**Learning outcomes**\n",
    "\n",
    "- State the three front-door conditions and check them in a DAG.\n",
    "- Derive and apply the front-door adjustment formula.\n",
    "- Compute front-door estimates with binary tables and as a product of regression coefficients.\n",
    "- Judge the plausibility of the front-door assumptions in applied settings."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Theory\n",
    "\n",
    "### When back-door fails\n",
    "\n",
    "Suppose $X \\leftarrow U \\to Y$ with $U$ unobserved. No back-door set exists. But if $X$ affects $Y$ **only** through a mediator $M$ that is not itself confounded with $X$, we can still identify the effect:\n",
    "\n",
    "$$\n",
    "U \\to X, \\quad U \\to Y, \\quad X \\to M \\to Y.\n",
    "$$\n",
    "\n",
    "Classic example: smoking $X$, tar deposits $M$, lung cancer $Y$, unobserved genotype $U$.\n",
    "\n",
    "### The criterion\n",
    "\n",
    "A set $\\mathbf M$ satisfies the **front-door criterion** relative to $(X, Y)$ if\n",
    "\n",
    "1. $\\mathbf M$ intercepts **all directed paths** from $X$ to $Y$;\n",
    "2. there is **no unblocked back-door path** from $X$ to $\\mathbf M$;\n",
    "3. **all back-door paths** from $\\mathbf M$ to $Y$ are blocked by $X$.\n",
    "\n",
    "### The formula\n",
    "\n",
    "If $\\mathbf M$ satisfies the criterion and $P(x, \\mathbf m) > 0$,\n",
    "\n",
    "$$\n",
    "P(y \\mid \\operatorname{do}(x)) = \\sum_{\\mathbf m} P(\\mathbf m \\mid x)\\sum_{x'} P(y \\mid \\mathbf m, x')\\,P(x').\n",
    "$$\n",
    "\n",
    "### The logic in two steps\n",
    "\n",
    "1. **Effect of $X$ on $M$.** By condition 2, $P(m \\mid \\operatorname{do}(x)) = P(m \\mid x)$.\n",
    "2. **Effect of $M$ on $Y$.** By condition 3, $X$ is a back-door set for $M \\to Y$: $P(y \\mid \\operatorname{do}(m)) = \\sum_{x'} P(y \\mid m, x')P(x')$.\n",
    "3. **Chain them.** By condition 1, $X$ affects $Y$ only via $M$: $P(y \\mid \\operatorname{do}(x)) = \\sum_m P(m \\mid \\operatorname{do}(x))\\,P(y \\mid \\operatorname{do}(m))$.\n",
    "\n",
    "### The linear case\n",
    "\n",
    "With $M = aX + e_M$ and $Y = bM + cU + e_Y$, $X$ correlated with $U$: estimate $a$ by regressing $M$ on $X$, and $b$ by regressing $Y$ on $M$ **and** $X$. The effect is $\\hat a\\hat b$.\n",
    "\n",
    "### Plausibility\n",
    "\n",
    "The conditions are strong: no direct effect of $X$ on $Y$ outside $M$, and no unobserved common cause of $M$ and $Y$. They hold most credibly when $M$ is a mechanical step: signing up versus attending, being offered versus receiving, prescribed versus dispensed.\n",
    "\n",
    "## Part 1 · Warm-up"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 1 · Check the conditions\n",
    "\n",
    "For each graph, does $\\{M\\}$ satisfy the front-door criterion for $X \\to Y$? (a) $U \\to X$, $U \\to Y$, $X \\to M \\to Y$. (b) Add $X \\to Y$. (c) Instead add $U \\to M$. (d) Instead add $V \\to M$, $V \\to Y$ with $V$ unobserved.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** (a) $M$ intercepts $X \\to M \\to Y$; back-door paths from $X$ to $M$: $X \\leftarrow U \\to Y \\leftarrow M$, blocked at the collider $Y$; back-door from $M$ to $Y$: $M \\leftarrow X \\leftarrow U \\to Y$, blocked by $X$. Satisfied.\n",
    "\n",
    "**Step 2.** (b) $X \\to Y$ is a directed path not through $M$: condition 1 fails.\n",
    "\n",
    "**Step 3.** (c) $X \\leftarrow U \\to M$ is an open back-door path from $X$ to $M$: condition 2 fails.\n",
    "\n",
    "**Step 4.** (d) $M \\leftarrow V \\to Y$ is a back-door path from $M$ to $Y$ not blocked by $X$: condition 3 fails.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 2 · Linear front door\n",
    "\n",
    "Regressing $M$ on $X$ gives 0.5; regressing $Y$ on $M$ and $X$ gives 2.0 on $M$. The front-door conditions hold. What is the effect of a one-unit increase in $X$?\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Effect of $X$ on $M$: 0.5. Effect of $M$ on $Y$: 2.0.\n",
    "\n",
    "**Answer:** $0.5 \\times 2.0 = 1.0$.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 3 · The first step\n",
    "\n",
    "Why is $P(m \\mid \\operatorname{do}(x)) = P(m \\mid x)$ in graph (a) of Exercise 1?\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** The only back-door path from $X$ to $M$ is $X \\leftarrow U \\to Y \\leftarrow M$.\n",
    "\n",
    "**Step 2.** It has a collider at $Y$, so it is blocked by the empty set.\n",
    "\n",
    "**Answer:** $\\varnothing$ satisfies the back-door criterion for $X \\to M$, so conditioning equals intervening.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 4 · A small front-door computation\n",
    "\n",
    "$P(X = 1) = 0.4$. $P(M = 1 \\mid X = 1) = 0.8$, $P(M = 1 \\mid X = 0) = 0.3$. $P(Y = 1 \\mid M, X)$: $(M, X) = (1, 1)$: 0.6; $(0, 1)$: 0.4; $(1, 0)$: 0.5; $(0, 0)$: 0.3. Compute $P(Y = 1 \\mid \\operatorname{do}(X = 1))$ by the front-door formula.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Inner sums: $m = 1$: $0.4(0.6) + 0.6(0.5) = 0.24 + 0.30 = 0.54$; $m = 0$: $0.4(0.4) + 0.6(0.3) = 0.16 + 0.18 = 0.34$.\n",
    "\n",
    "**Step 2.** Outer sum with $P(m \\mid X = 1)$: $0.8(0.54) + 0.2(0.34) = 0.432 + 0.068 = 0.50$.\n",
    "\n",
    "**Answer:** 0.50.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 5 · Why average over $x'$?\n",
    "\n",
    "In the second step, why do we average $P(y \\mid m, x')$ over $P(x')$ instead of using $P(y \\mid m, x)$ at the treatment value $x$?\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** We need the effect of $M$ on $Y$ in the *whole population*, $P(y \\mid \\operatorname{do}(m))$.\n",
    "\n",
    "**Step 2.** $X$ is used only as a back-door adjustment variable for $M \\to Y$ (it blocks $M \\leftarrow X \\leftarrow U \\to Y$), so it must be averaged over its marginal distribution, as in any back-door formula.\n",
    "\n",
    "**Answer:** using $P(y \\mid m, x)$ alone would describe the $U$-mix of people with $X = x$, which is confounded.\n",
    "\n",
    "</details>\n",
    "\n",
    "## Part 2 · Proofs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 6 · Step 1 in general\n",
    "\n",
    "Prove $P(m \\mid \\operatorname{do}(x)) = P(m \\mid x)$ under condition 2.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Condition 2 says $\\varnothing$ blocks every back-door path from $X$ to $M$, and $\\varnothing$ contains no descendants of $X$.\n",
    "\n",
    "**Step 2.** So $\\varnothing$ satisfies the back-door criterion for $(X, M)$, and the adjustment formula reduces to $P(m \\mid \\operatorname{do}(x)) = P(m \\mid x)$. $\\blacksquare$\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 7 · Step 2 in general\n",
    "\n",
    "Prove $P(y \\mid \\operatorname{do}(m)) = \\sum_{x} P(y \\mid m, x)P(x)$ under the front-door conditions.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Condition 3: $\\{X\\}$ blocks all back-door paths from $M$ to $Y$.\n",
    "\n",
    "**Step 2.** $X$ is not a descendant of $M$: $M$ lies on the directed paths from $X$, and acyclicity forbids $M \\to \\cdots \\to X$.\n",
    "\n",
    "**Step 3.** So $\\{X\\}$ satisfies the back-door criterion for $(M, Y)$ and the adjustment formula gives the result. $\\blacksquare$\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 8 · Chaining the steps\n",
    "\n",
    "Using the SCM $U \\to X$, $U \\to Y$, $X \\to M \\to Y$, prove $P(y \\mid \\operatorname{do}(x)) = \\sum_m P(m \\mid \\operatorname{do}(x))P(y \\mid \\operatorname{do}(m))$.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Truncated factorisation for $\\operatorname{do}(x)$: $P(u, m, y \\mid \\operatorname{do}(x)) = P(u)P(m \\mid x)P(y \\mid m, u)$.\n",
    "\n",
    "**Step 2.** Sum over $u$ and $m$: $P(y \\mid \\operatorname{do}(x)) = \\sum_m P(m \\mid x)\\sum_u P(y \\mid m, u)P(u)$.\n",
    "\n",
    "**Step 3.** Truncated factorisation for $\\operatorname{do}(m)$: $P(u, x, y \\mid \\operatorname{do}(m)) = P(u)P(x \\mid u)P(y \\mid m, u)$; summing over $u, x$ gives $P(y \\mid \\operatorname{do}(m)) = \\sum_u P(y \\mid m, u)P(u)$.\n",
    "\n",
    "**Step 4.** By Exercise 6, $P(m \\mid x) = P(m \\mid \\operatorname{do}(x))$. Substitute both into Step 2. $\\blacksquare$ Combined with Exercise 7, this proves the front-door formula.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 9 · The linear product\n",
    "\n",
    "With $X = gU + e_X$, $M = aX + e_M$, $Y = bM + cU + e_Y$ (all disturbances independent), prove the population coefficient on $M$ in the regression of $Y$ on $(M, X)$ is $b$, so $\\hat a\\hat b \\to ab$.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** $E[Y \\mid M, X] = bM + cE[U \\mid M, X]$.\n",
    "\n",
    "**Step 2.** $M = aX + e_M$ with $e_M$ independent of $(U, X)$; so given $X$, $M$ carries no information about $U$: $E[U \\mid M, X] = E[U \\mid X]$, a linear function of $X$ only.\n",
    "\n",
    "**Step 3.** So $E[Y \\mid M, X] = bM + (\\text{function of } X)$: the coefficient on $M$ is $b$.\n",
    "\n",
    "**Step 4.** The regression of $M$ on $X$ recovers $a$ because $e_M$ is independent of $X$. $\\blacksquare$\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 10 · A direct effect breaks it\n",
    "\n",
    "Add a direct effect, $Y = bM + dX + cU + e_Y$. What does $\\hat a\\hat b$ converge to, and what is the true total effect?\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** The argument of Exercise 9 still gives coefficient $b$ on $M$ (the $dX$ term is a function of $X$).\n",
    "\n",
    "**Step 2.** So $\\hat a\\hat b \\to ab$.\n",
    "\n",
    "**Step 3.** True total effect: $ab + d$.\n",
    "\n",
    "**Answer:** the front-door estimate misses the direct effect entirely; condition 1 is essential. $\\blacksquare$\n",
    "\n",
    "</details>\n",
    "\n",
    "## Part 3 · Applied"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 11 · Smoking, tar and cancer\n",
    "\n",
    "Observational data: $P(X = 1) = 0.5$ smokers; $P(M = 1 \\mid X = 1) = 0.9$ and $P(M = 1 \\mid X = 0) = 0.1$ heavy tar deposits; cancer rates $P(Y = 1 \\mid M, X)$: $(1, 1)$: 0.44; $(0, 1)$: 0.34; $(1, 0)$: 0.26; $(0, 0)$: 0.16. Compute the naive risk difference and the front-door effect.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Naive: $P(Y = 1 \\mid X = 1) = 0.9(0.44) + 0.1(0.34) = 0.43$ and $P(Y = 1 \\mid X = 0) = 0.1(0.26) + 0.9(0.16) = 0.17$; difference 0.26.\n",
    "\n",
    "**Step 2.** Effect of tar, averaging over smoking: $P(Y \\mid \\operatorname{do}(M = 1)) = 0.5(0.44) + 0.5(0.26) = 0.35$; $P(Y \\mid \\operatorname{do}(M = 0)) = 0.5(0.34) + 0.5(0.16) = 0.25$.\n",
    "\n",
    "**Step 3.** $P(Y \\mid \\operatorname{do}(X = 1)) = 0.9(0.35) + 0.1(0.25) = 0.34$.\n",
    "\n",
    "**Step 4.** $P(Y \\mid \\operatorname{do}(X = 0)) = 0.1(0.35) + 0.9(0.25) = 0.26$.\n",
    "\n",
    "**Answer:** causal risk difference 0.08, versus a naive 0.26. (These numbers come from an SCM with a genotype that raises both smoking and cancer; the code checks that 0.08 is the true effect.)\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 12 · Signing up versus showing up\n",
    "\n",
    "A job-training programme records who **signed up** ($X$), who **attended** ($M$) and later earnings ($Y$). Motivation is unobserved. Discuss each front-door condition for the effect of signing up.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Condition 1: signing up should affect earnings only through attending. Plausible, unless signing up itself changes behaviour (e.g. it triggers other services or job-search requirements).\n",
    "\n",
    "**Step 2.** Condition 2: no unblocked confounding between signing up and attending. Questionable: motivated people both sign up and attend.\n",
    "\n",
    "**Step 3.** Condition 3: given sign-up status, attendance is unconfounded with earnings. Questionable for the same reason.\n",
    "\n",
    "**Answer:** among sign-ups, attendance is nearly mechanical (most attend), which makes conditions 2–3 more credible; studies of this design find estimates close to experimental benchmarks for some groups and not others. Plausibility must be argued, not assumed.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 13 · Mobile money and savings\n",
    "\n",
    "A mobile-money app ($X$, adopted or not) can raise household savings $Y$ only through account usage $M$ (deposits per month). Adoption and savings share unobserved financial literacy. Regressions: usage on adoption gives 0.6 deposits; savings on usage and adoption gives \\$50 per deposit on usage. Estimate the effect of adoption on savings.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Effect of adoption on usage: $\\hat a = 0.6$.\n",
    "\n",
    "**Step 2.** Effect of usage on savings, adjusting for adoption: $\\hat b = 50$.\n",
    "\n",
    "**Answer:** $0.6 \\times 50 = 30$ dollars per household. The naive regression of savings on adoption would also pick up literacy.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 14 · What if the app has a direct effect?\n",
    "\n",
    "Suppose the app also raises savings by \\$10 directly (through reminders that work even without deposits). What does the front-door estimate from Exercise 13 converge to, and what is the true effect?\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** By Exercise 10, $\\hat a\\hat b \\to ab = 30$.\n",
    "\n",
    "**Step 2.** True total effect: $30 + 10 = 40$.\n",
    "\n",
    "**Answer:** the front-door estimate understates the effect by \\$10 (25%). In the simulation below, the front-door estimate is about 30 and the truth 40.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Your work:*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise 15 · Nurse staffing and mortality\n",
    "\n",
    "A hospital network asks whether higher nurse staffing ($X$) lowers in-hospital mortality ($Y$). Wards with sicker patients get more nurses, and severity is poorly measured. The analyst proposes using monitoring frequency ($M$, vital-sign checks per day) as a front-door variable. List what must be true and one likely failure for each condition.\n",
    "\n",
    "<details><summary>Show solution</summary>\n",
    "\n",
    "**Step 1.** Condition 1: staffing affects mortality only through monitoring. Likely failure: nurses also deliver medication on time and prevent falls; those channels bypass $M$.\n",
    "\n",
    "**Step 2.** Condition 2: nothing unmeasured affects both staffing and monitoring. Likely failure: severity raises staffing and monitoring protocols directly.\n",
    "\n",
    "**Step 3.** Condition 3: given staffing, monitoring is unconfounded with mortality. Likely failure: sicker patients are monitored more and die more.\n",
    "\n",
    "**Answer:** monitoring fails all three; the front door is not credible here. A better design might use staffing shocks (nurse absences, Phase 5 instruments).\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 itertools\n",
    "import numpy as np\n",
    "\n",
    "# Exercise 11: the SCM behind the table (U = genotype, unobserved)\n",
    "pU = .5; pX = {1: .8, 0: .2}; pM = {1: .9, 0: .1}; pY = {(0, 0): .1, (1, 0): .2, (0, 1): .4, (1, 1): .5}   # key (m, u)\n",
    "truth = {x: sum((pM[x] if m else 1 - pM[x]) * sum((pU if u else 1 - pU) * pY[m, u] for u in (0, 1)) for m in (0, 1)) for x in (0, 1)}\n",
    "J = {(u, x, m): (pU if u else 1 - pU) * (pX[u] if x else 1 - pX[u]) * (pM[x] if m else 1 - pM[x])\n",
    "     for u, x, m in itertools.product((0, 1), repeat=3)}\n",
    "Px = {x: sum(v for k, v in J.items() if k[1] == x) for x in (0, 1)}\n",
    "Pm_x = {(m, x): sum(v for k, v in J.items() if k[1] == x and k[2] == m) / Px[x] for m in (0, 1) for x in (0, 1)}\n",
    "Py_mx = {(m, x): sum(v * pY[m, k[0]] for k, v in J.items() if k[1] == x and k[2] == m) / (Px[x] * Pm_x[m, x]) for m in (0, 1) for x in (0, 1)}\n",
    "print(\"observed P(Y=1|M,X):\", {k: round(v, 3) for k, v in Py_mx.items()})\n",
    "naive = {x: sum(Pm_x[m, x] * Py_mx[m, x] for m in (0, 1)) for x in (0, 1)}\n",
    "front = {x: sum(Pm_x[m, x] * sum(Py_mx[m, xp] * Px[xp] for xp in (0, 1)) for m in (0, 1)) for x in (0, 1)}\n",
    "print(f\"naive {naive[1] - naive[0]:.3f}  front-door {front[1] - front[0]:.3f}  truth {truth[1] - truth[0]:.3f}\")\n",
    "\n",
    "# Exercises 13-14: linear front door with and without a direct effect\n",
    "rng = np.random.default_rng(54); n = 500_000\n",
    "L = rng.normal(size=n)                                 # financial literacy (unobserved)\n",
    "X = (L + rng.normal(size=n) > 0).astype(float)\n",
    "M = 0.6 * X + rng.normal(size=n)\n",
    "for d in (0, 10):\n",
    "    Y = 50 * M + d * X + 40 * L + rng.normal(0, 20, n)\n",
    "    a = np.polyfit(X, M, 1)[0]\n",
    "    b = np.linalg.lstsq(np.column_stack([np.ones(n), M, X]), Y, rcond=None)[0][1]\n",
    "    naive = Y[X == 1].mean() - Y[X == 0].mean()\n",
    "    print(f\"direct effect {d}: naive {naive:.1f}, front-door {a * b:.1f}, truth {30 + d}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Key takeaways\n",
    "\n",
    "- The front door identifies an effect despite unobserved treatment–outcome confounding, if a mediator captures the whole effect, is unconfounded with the treatment, and is unconfounded with the outcome given the treatment.\n",
    "- Formula: $P(y \\mid \\operatorname{do}(x)) = \\sum_m P(m \\mid x)\\sum_{x'} P(y \\mid m, x')P(x')$; in linear models, the product of two regression coefficients.\n",
    "- Its assumptions are strong; they are most credible when the mediator is a mechanical step of the treatment."
   ]
  }
 ]
}