{
 "nbformat": 4,
 "nbformat_minor": 5,
 "metadata": {
  "colab": {
   "provenance": [],
   "toc_visible": true
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "cells": [
  {
   "id": "m10-l2-0",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Lesson 10.2 · Optimal Bandwidths and Robust Inference with rdrobust\n",
    "\n",
    "*Accelerated Foundation in Applied Causal Inference*\n",
    "\n",
    "**By the end of this lesson you can:**\n",
    "\n",
    "- Use rdrobust for MSE-optimal bandwidths and bias-corrected robust confidence intervals\n",
    "- Estimate the incumbency advantage in US House elections (Lee, Moretti and Butler)\n",
    "- Run the standard robustness checks: bandwidth sensitivity, placebo cutoffs, covariate continuity\n",
    "- Present an RD analysis in a reproducible table\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": "m10-l2-1",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Why bandwidth choice needs theory"
   ]
  },
  {
   "id": "m10-l2-2",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "!pip install -q numpy pandas rdrobust matplotlib causaldata\n",
    "import numpy as np, pandas as pd\n",
    "from rdrobust import rdrobust, rdbwselect, rdplot\n",
    "import matplotlib.pyplot as plt\n",
    "rng = np.random.default_rng(2026)"
   ]
  },
  {
   "id": "m10-l2-3",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## rdrobust on the grant data"
   ]
  },
  {
   "id": "m10-l2-4",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "N, CUTOFF = 3_000, 50\n",
    "score = rng.uniform(20, 80, N)\n",
    "grant = (score >= CUTOFF).astype(int)\n",
    "revenue = 100_000 - 500 * score + 4 * (score - 50) ** 2 + 12_000 * grant + rng.normal(0, 6_000, N)\n",
    "\n",
    "rd = rdrobust(y=revenue, x=score, c=CUTOFF)\n",
    "print(rd)"
   ]
  },
  {
   "id": "m10-l2-5",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Close elections: does the winning party change how members vote?"
   ]
  },
  {
   "id": "m10-l2-6",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **Question it answers:** Does narrowly electing a Democrat make the district's representative vote more liberally later?"
   ]
  },
  {
   "id": "m10-l2-7",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "from causaldata import close_elections_lmb\n",
    "lmb = close_elections_lmb.load_pandas().data.dropna(subset=[\"score\", \"lagdemvoteshare\"])\n",
    "lmb[\"margin\"] = lmb.lagdemvoteshare - 0.5          # > 0: the Democrat won last time\n",
    "\n",
    "rd_lmb = rdrobust(y=lmb.score, x=lmb.margin, c=0)\n",
    "print(f\"Jump in ADA score: {rd_lmb.coef.iloc[0, 0]:.1f} points | robust 95% CI \"\n",
    "      f\"[{rd_lmb.ci.iloc[2, 0]:.1f}, {rd_lmb.ci.iloc[2, 1]:.1f}] | h = {rd_lmb.bws.iloc[0, 0]:.3f}\")\n",
    "\n",
    "rdplot(y=lmb.score, x=lmb.margin, c=0, binselect=\"es\", nbins=[30, 30],\n",
    "       title=\"Close elections: ADA score of the next representative\", x_label=\"Democratic margin\", y_label=\"ADA score\")\n",
    "plt.show()"
   ]
  },
  {
   "id": "m10-l2-8",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Robustness checks"
   ]
  },
  {
   "id": "m10-l2-9",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **Question it answers:** Is the estimated jump stable across bandwidths, and absent at fake cutoffs and for pre-determined variables?"
   ]
  },
  {
   "id": "m10-l2-10",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "h_opt = rd_lmb.bws.iloc[0, 0]\n",
    "for mult in [0.5, 0.75, 1, 1.5, 2]:\n",
    "    r = rdrobust(y=lmb.score, x=lmb.margin, c=0, h=h_opt * mult)\n",
    "    print(f\"h = {h_opt * mult:.3f}: {r.coef.iloc[0, 0]:6.2f}  robust CI [{r.ci.iloc[2, 0]:6.2f}, {r.ci.iloc[2, 1]:6.2f}]\")"
   ]
  },
  {
   "id": "m10-l2-11",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "for fake in [-0.2, -0.1, 0.1, 0.2]:\n",
    "    side = lmb[lmb.margin < 0] if fake < 0 else lmb[lmb.margin > 0]\n",
    "    r = rdrobust(y=side.score, x=side.margin, c=fake)\n",
    "    print(f\"placebo cutoff {fake:+.1f}: {r.coef.iloc[0, 0]:6.2f} (robust p = {r.pv.iloc[2, 0]:.3f})\")"
   ]
  },
  {
   "id": "m10-l2-12",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "r = rdrobust(y=lmb.year, x=lmb.margin, c=0)\n",
    "print(f\"Jump in election year at the cutoff: {r.coef.iloc[0, 0]:.2f} (robust p = {r.pv.iloc[2, 0]:.3f})\")"
   ]
  },
  {
   "id": "m10-l2-13",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✍️ Exercise\n",
    "\n",
    "Re-estimate the incumbency jump with a **local quadratic** (`p=2`) and with a **uniform** kernel\n",
    "(`kernel=\"uniform\"`). Put all results (baseline, p = 2, uniform, half and double bandwidth) in one table\n",
    "with robust CIs. Would any of them change the conclusion?"
   ]
  },
  {
   "id": "m10-l2-14",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Your answer here"
   ]
  },
  {
   "id": "m10-l2-15",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Checklist and recap"
   ]
  }
 ]
}