{
 "nbformat": 4,
 "nbformat_minor": 5,
 "metadata": {
  "colab": {
   "provenance": [],
   "toc_visible": true
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "cells": [
  {
   "id": "m09-l3-0",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Lesson 9.3 · The Exclusion Restriction, Endogeneity Tests and Over-Identification\n",
    "\n",
    "*Accelerated Foundation in Applied Causal Inference*\n",
    "\n",
    "**By the end of this lesson you can:**\n",
    "\n",
    "- Simulate violations of the exclusion restriction and see how far they move the estimate\n",
    "- Run and interpret the Wu–Hausman test of endogeneity\n",
    "- Use two instruments and interpret the Sargan over-identification test, including its limits\n",
    "- Write the \"defence of the instrument\" section of an IV report\n",
    "\n",
    "> Read the full explanations on the course website, then run each cell here in order (**Runtime → Run all** works on a fresh runtime)."
   ]
  },
  {
   "id": "m09-l3-1",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The Achilles heel: exclusion"
   ]
  },
  {
   "id": "m09-l3-2",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "!pip install -q numpy pandas linearmodels statsmodels causaldata\n",
    "import numpy as np, pandas as pd\n",
    "from linearmodels.iv import IV2SLS\n",
    "rng = np.random.default_rng(9)"
   ]
  },
  {
   "id": "m09-l3-3",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **Question it answers:** How badly is the return to schooling biased if living near a college raises wages directly?"
   ]
  },
  {
   "id": "m09-l3-4",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def iv_with_violation(direct_effect, n=20_000, first_stage=1.0):\n",
    "    a = rng.normal(size=n); z = rng.binomial(1, 0.5, n)\n",
    "    educ = 12 + first_stage * z + 2 * a + rng.normal(size=n)\n",
    "    y = 25_000 + 3_000 * educ + 5_000 * a + direct_effect * z + rng.normal(0, 4_000, n)\n",
    "    d = pd.DataFrame({\"y\": y, \"educ\": educ, \"z\": z, \"const\": 1.0})\n",
    "    return IV2SLS(d.y, d.const, d.educ, d.z).fit().params[\"educ\"]\n",
    "\n",
    "for direct in [0, 250, 500, 1_000]:\n",
    "    for fs in [1.0, 0.3]:\n",
    "        print(f\"direct effect ${direct:>5} | first stage {fs}: 2SLS = {iv_with_violation(direct, first_stage=fs):,.0f}\")"
   ]
  },
  {
   "id": "m09-l3-5",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Is the treatment endogenous at all? Wu–Hausman"
   ]
  },
  {
   "id": "m09-l3-6",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "from causaldata import close_college\n",
    "card = close_college.load_pandas().data.dropna()\n",
    "iv_card = IV2SLS.from_formula(\n",
    "    \"lwage ~ 1 + exper + I(exper**2) + black + south + smsa + married + [educ ~ nearc4]\", card\n",
    ").fit(cov_type=\"robust\")\n",
    "print(iv_card.wu_hausman())"
   ]
  },
  {
   "id": "m09-l3-7",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Two instruments and over-identification"
   ]
  },
  {
   "id": "m09-l3-8",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **Question it answers:** Do two different instruments for schooling tell the same story?"
   ]
  },
  {
   "id": "m09-l3-9",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "N = 10_000\n",
    "a = rng.normal(size=N)\n",
    "z1 = rng.binomial(1, 0.5, N)                    # near a college (valid)\n",
    "z2 = rng.normal(size=N)                         # tuition subsidy shock (valid... or not)\n",
    "for violation in [0, 1_500]:\n",
    "    educ = 12 + 1.0 * z1 + 0.8 * z2 + 2 * a + rng.normal(size=N)\n",
    "    y = 25_000 + 3_000 * educ + 5_000 * a + violation * z2 + rng.normal(0, 4_000, N)\n",
    "    d = pd.DataFrame({\"y\": y, \"educ\": educ, \"z1\": z1, \"z2\": z2, \"const\": 1.0})\n",
    "    r = IV2SLS(d.y, d.const, d.educ, d[[\"z1\", \"z2\"]]).fit()\n",
    "    print(f\"z2 direct effect {violation:>5}: 2SLS = {r.params['educ']:,.0f} | Sargan p = {r.sargan.pval:.3f}\")"
   ]
  },
  {
   "id": "m09-l3-10",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Workshop: defending an instrument"
   ]
  },
  {
   "id": "m09-l3-11",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✍️ Exercise\n",
    "\n",
    "Write a half-page defence of an instrument for your capstone or for one of these: rainfall shocks for\n",
    "agricultural income; judge leniency for incarceration; college proximity for schooling (Card). Cover:\n",
    "\n",
    "1. **Relevance**: the first-stage mechanism and how you would show it.\n",
    "2. **Independence**: why Z is as good as random (or random given which covariates).\n",
    "3. **Exclusion**: list every other path from Z to Y you can think of and argue each away, or bound it.\n",
    "4. **Monotonicity**: could anyone respond in the opposite direction?\n",
    "5. **Who are the compliers**, and is their LATE the effect a policy-maker needs?\n",
    "\n",
    "Then run the code below for Card's instrument: how large would a direct effect of growing up near a\n",
    "college on log wages need to be to change the conclusion?"
   ]
  },
  {
   "id": "m09-l3-12",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Your answer here"
   ]
  },
  {
   "id": "m09-l3-13",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Checklist and recap"
   ]
  }
 ]
}