#!/usr/bin/env python3 """Figure ch08 — effet d'un rezonage mesuré par ventes appariées et par doubles différences (DONNÉES SYNTHÉTIQUES). (a) différence traité − témoin ajusté (% du témoin) selon l'âge de la maison, avec moyenne et médiane ; (b) ln P moyen des ventes H1 traitées et témoins avant/après T0 (schéma 2 × 2) et contrefactuel. Sortie : assets/figures/ch08/ch08-fig-rezonage-paires.pdf""" import sys, pathlib ROOT = pathlib.Path(__file__).resolve().parents[3] sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) import numpy as np, matplotlib.pyplot as plt from traite.style import setup, palette, savefig, LARGEUR import ch08_zonage as cas def main(): setup() lots, ventes = cas.charger_ou_generer(ecrire=False) paires = cas.apparier(ventes); rp = cas.resume_paires(paires); d = cas.did(ventes) fig, (a, b) = plt.subplots(1, 2, figsize=(LARGEUR, 3.1), gridspec_kw={"width_ratios": [1.25, 1]}) x = paires["age_traite"].to_numpy(); y = paires["difference_pct"].to_numpy() * 100 a.scatter(x, y, s=18, color=palette.cat[0], linewidths=0, zorder=3) xx = np.linspace(x.min(), x.max(), 50); pente, orig = np.polyfit(x, y, 1) a.plot(xx, orig + pente * xx, color=palette.cat[0], linewidth=1.2) a.plot(xx, (np.exp([cas.prime_rezonage(v) for v in xx]) - 1) * 100, color=palette.ink2, linestyle="--", linewidth=1.0) a.axhline(rp["moyenne_pct"] * 100, color=palette.cat[1], linewidth=1.0) a.axhline(rp["mediane_pct"] * 100, color=palette.cat[1], linewidth=1.0, linestyle=":") a.text(x.max(), rp["moyenne_pct"] * 100 + 1.2, f"moyenne {rp['moyenne_pct']*100:.1f} %", ha="right", fontsize=7.5, color=palette.ink) a.text(x.max(), rp["mediane_pct"] * 100 - 3.2, f"médiane {rp['mediane_pct']*100:.1f} %", ha="right", fontsize=7.5, color=palette.ink) a.text(x.max(), y.min() + 1.0, "tireté : prime injectée\n(vérité du générateur)", ha="right", fontsize=7, color=palette.ink2) a.set_xlabel("Âge de la maison traitée (années)"); a.set_ylabel("Traité − témoin ajusté (% du témoin)") a.set_title(f"(a) {rp['n']} paires appariées", loc="left") m = d["moyennes_lnP"] xs = [0, 1] b.plot(xs, [m[(False, False)], m[(False, True)]], marker="o", color=palette.cat[2], label="témoins (reste de H1)") b.plot(xs, [m[(True, False)], m[(True, True)]], marker="o", color=palette.cat[0], label="traités (îlot rezoné)") cf = m[(True, False)] + (m[(False, True)] - m[(False, False)]) b.plot(xs, [m[(True, False)], cf], marker="o", markerfacecolor="none", color=palette.cat[0], linestyle="--", label="contrefactuel des traités") b.annotate("", xy=(1, m[(True, True)]), xytext=(1, cf), arrowprops=dict(arrowstyle="<->", color=palette.ink, lw=1.0)) b.text(1.03, (m[(True, True)] + cf) / 2, f"δ = {d['delta_2x2']:.3f}\n(2 × 2)", fontsize=7.5, va="center", color=palette.ink) b.set_xticks(xs); b.set_xticklabels(["avant T0", "après T0"]); b.set_ylabel("ln P moyen (ventes H1)") b.set_xlim(-0.3, 1.5); b.set_title("(b) doubles différences", loc="left"); b.legend(loc="upper left", fontsize=7) fig.tight_layout() savefig(fig, "ch08/ch08-fig-rezonage-paires.pdf") if __name__ == "__main__": main()