{
 "nbformat": 4,
 "nbformat_minor": 5,
 "metadata": {
  "colab": {
   "provenance": [],
   "toc_visible": true
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "cells": [
  {
   "id": "m12-l2-0",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Lesson 12.2 · Inference for Synthetic Controls: Placebos in Space and Time\n",
    "\n",
    "*Accelerated Foundation in Applied Causal Inference*\n",
    "\n",
    "**By the end of this lesson you can:**\n",
    "\n",
    "- Run in-space placebo tests by re-assigning treatment to every donor\n",
    "- Compute post/pre RMSPE ratios and a permutation p-value\n",
    "- Run in-time placebos with a fake treatment date\n",
    "- Check robustness with leave-one-out donor pools\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": "m12-l2-1",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Why standard errors do not apply"
   ]
  },
  {
   "id": "m12-l2-2",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "!pip install -q numpy pandas scipy matplotlib\n",
    "import numpy as np, pandas as pd\n",
    "from scipy.optimize import minimize\n",
    "import matplotlib.pyplot as plt\n",
    "rng = np.random.default_rng(42)\n",
    "\n",
    "def synth_weights(y_pre, Y_pre):\n",
    "    J = Y_pre.shape[0]\n",
    "    res = minimize(lambda w: np.sum((y_pre - w @ Y_pre) ** 2), np.full(J, 1 / J), method=\"SLSQP\",\n",
    "                   bounds=[(0, 1)] * J, constraints={\"type\": \"eq\", \"fun\": lambda w: w.sum() - 1})\n",
    "    return res.x\n",
    "\n",
    "# Same world as Lesson 12.1, with a larger donor pool\n",
    "years = np.arange(2005, 2025); T0 = 2018; pre = years < T0; J = 15\n",
    "factors = np.column_stack([np.ones(len(years)), (years - 2005) / 10, np.sin((years - 2005) / 3)])\n",
    "donors = rng.uniform([80, 0, -5], [140, 30, 10], size=(J, 3)) @ factors.T + rng.normal(0, 1.5, (J, len(years)))\n",
    "treated = np.array([0.4, 0.5, 0.1] + [0] * (J - 3)) @ donors + np.where(years >= T0, -15.0, 0) + rng.normal(0, 1, len(years))\n",
    "units = np.vstack([treated, donors])                                     # row 0 = Province A\n",
    "names = [\"Province A\"] + [f\"Donor {j}\" for j in range(J)]"
   ]
  },
  {
   "id": "m12-l2-3",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## In-space placebos"
   ]
  },
  {
   "id": "m12-l2-4",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **Question it answers:** Is Province A's post-2018 drop larger than the drops we would \"find\" in provinces that had no programme?"
   ]
  },
  {
   "id": "m12-l2-5",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def gap_for(i, all_units, pre_mask):\n",
    "    others = [k for k in range(len(all_units)) if k != i and k != 0]   # never use the real treated unit as donor\n",
    "    w = synth_weights(all_units[i, pre_mask], all_units[others][:, pre_mask])\n",
    "    return all_units[i] - w @ all_units[others]\n",
    "\n",
    "gaps = np.array([gap_for(i, units, pre) for i in range(len(units))])\n",
    "\n",
    "plt.figure(figsize=(10, 5))\n",
    "for i in range(1, len(units)):\n",
    "    plt.plot(years, gaps[i], color=\"grey\", alpha=.4)\n",
    "plt.plot(years, gaps[0], color=\"red\", lw=3, label=\"Province A\")\n",
    "plt.axvline(T0, color=\"black\", ls=\":\"); plt.axhline(0, color=\"black\", lw=.5)\n",
    "plt.title(\"In-space placebos: gap for every unit\"); plt.legend(); plt.show()"
   ]
  },
  {
   "id": "m12-l2-6",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## RMSPE ratios and a permutation p-value"
   ]
  },
  {
   "id": "m12-l2-7",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "rmspe = lambda g: np.sqrt(np.mean(g ** 2))\n",
    "ratios = pd.Series([rmspe(g[~pre]) / rmspe(g[pre]) for g in gaps], index=names).sort_values(ascending=False)\n",
    "p_value = (ratios >= ratios[\"Province A\"]).mean()\n",
    "print(ratios.round(2).head(6))\n",
    "print(f\"Permutation p-value: {p_value:.3f} (smallest possible with {len(units)} units: {1 / len(units):.3f})\")"
   ]
  },
  {
   "id": "m12-l2-8",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## In-time placebos and leave-one-out"
   ]
  },
  {
   "id": "m12-l2-9",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "fake_T0 = 2013\n",
    "fake_pre = years < fake_T0\n",
    "window = years < T0\n",
    "w_fake = synth_weights(treated[fake_pre], donors[:, fake_pre])\n",
    "fake_gap = (treated - w_fake @ donors)[window]\n",
    "print(f\"Average 'effect' of the fake 2013 programme (2013-2017): {fake_gap[years[window] >= fake_T0].mean():.2f}\")"
   ]
  },
  {
   "id": "m12-l2-10",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "w = synth_weights(treated[pre], donors[:, pre])\n",
    "for j in np.where(w > 0.01)[0]:\n",
    "    keep = [k for k in range(J) if k != j]\n",
    "    w_j = synth_weights(treated[pre], donors[keep][:, pre])\n",
    "    print(f\"without Donor {j:>2} (weight {w[j]:.2f}): effect {(treated - w_j @ donors[keep])[~pre].mean():.2f}\")"
   ]
  },
  {
   "id": "m12-l2-11",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise"
   ]
  },
  {
   "id": "m12-l2-12",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✍️ Exercise\n",
    "\n",
    "Re-run the in-space placebo analysis with a **true effect of −3** instead of −15. What happens to Province\n",
    "A's rank and p-value? What does that imply for evaluating modest policies with synthetic control?"
   ]
  },
  {
   "id": "m12-l2-13",
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Your answer here"
   ]
  },
  {
   "id": "m12-l2-14",
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Checklist and recap"
   ]
  }
 ]
}