Skip to content

Commit 99c0428

Browse files
igerberclaude
andcommitted
spillover-tva: address CI codex R7 P2 — 2-decimal precision contract
R7 noted that the notebook, README, and CHANGELOG publish 2-decimal numbers (-4.29, -7.34, -4.53, -5.38, -2.59) but the drift tests only enforce round-to-1 precision. Future drift smaller than 0.05 could leave the prose stale without failing any test. Mixed fix: - WELL-CONDITIONED headline pins tightened to round-to-2 (matching what the prose actually quotes): * test_naive_att_endpoint_matches_quoted: -4.3 → -4.29 * test_spillover_did_recovers_tau_total: -7.3 → -7.34 * test_spillover_did_recovers_delta_1: -4.5 → -4.53 - BORDERLINE rings=[0,50] grid point left at round-to-1 (per the R5 reviewer's BLAS-safety guidance). Notebook §4 prose coarsened to match: "-5.38" → "~-5.4" and "-2.59" → "~-2.6", with an explicit parenthetical explaining WHY this point uses 1-decimal precision while §5 uses 2 (borderline-rank-deficient design under d_bar=50 can shift across BLAS paths). Headline values are stable across BLAS paths to better than 0.005 (verified on Apple Silicon; cross-platform variance on well- conditioned fits is sub-ULP). The drift-test docstrings now explicitly document the 2-decimal vs 1-decimal contract for future maintainers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 948627b commit 99c0428

2 files changed

Lines changed: 40 additions & 26 deletions

File tree

docs/tutorials/23_spillover_tva.ipynb

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"cells": [
33
{
44
"cell_type": "markdown",
5-
"id": "94144842",
5+
"id": "e918f4ab",
66
"metadata": {},
77
"source": [
88
"# Spillover-aware DiD with `SpilloverDiD` \u2014 a TVA-style worked example\n",
@@ -47,7 +47,7 @@
4747
},
4848
{
4949
"cell_type": "markdown",
50-
"id": "25550a15",
50+
"id": "793f0506",
5151
"metadata": {},
5252
"source": [
5353
"## 2. The synthetic panel\n",
@@ -75,7 +75,7 @@
7575
{
7676
"cell_type": "code",
7777
"execution_count": null,
78-
"id": "1d5920a5",
78+
"id": "44843f78",
7979
"metadata": {},
8080
"outputs": [],
8181
"source": [
@@ -157,7 +157,7 @@
157157
{
158158
"cell_type": "code",
159159
"execution_count": null,
160-
"id": "56a1f7e8",
160+
"id": "777da50e",
161161
"metadata": {},
162162
"outputs": [],
163163
"source": [
@@ -180,7 +180,7 @@
180180
},
181181
{
182182
"cell_type": "markdown",
183-
"id": "55bde532",
183+
"id": "a06e2dfa",
184184
"metadata": {},
185185
"source": [
186186
"## 3. The naive headline \u2014 multi-period TWFE on the full sample\n",
@@ -194,7 +194,7 @@
194194
{
195195
"cell_type": "code",
196196
"execution_count": null,
197-
"id": "bfef8286",
197+
"id": "bd3092be",
198198
"metadata": {},
199199
"outputs": [],
200200
"source": [
@@ -224,7 +224,7 @@
224224
},
225225
{
226226
"cell_type": "markdown",
227-
"id": "f48bbaf7",
227+
"id": "9ec6a20e",
228228
"metadata": {},
229229
"source": [
230230
"The naive estimate is roughly **-4.29**, about 58% of the true\n",
@@ -241,7 +241,7 @@
241241
},
242242
{
243243
"cell_type": "markdown",
244-
"id": "389eca88",
244+
"id": "8ab9fcf7",
245245
"metadata": {},
246246
"source": [
247247
"## 4. Choosing the spillover bandwidth\n",
@@ -267,7 +267,7 @@
267267
{
268268
"cell_type": "code",
269269
"execution_count": null,
270-
"id": "771bdfd6",
270+
"id": "e768b4ce",
271271
"metadata": {},
272272
"outputs": [],
273273
"source": [
@@ -294,19 +294,22 @@
294294
},
295295
{
296296
"cell_type": "markdown",
297-
"id": "ee054b67",
297+
"id": "1467301a",
298298
"metadata": {},
299299
"source": [
300300
"At `d_bar = 50` km the ring is too narrow: near-controls in the\n",
301301
"50-78 km band ARE exposed in the DGP (they're within the true 100 km\n",
302302
"spillover horizon and carry $\\delta_1 = -4.5$), but the ring spec\n",
303303
"misclassifies them as far-away clean controls. Both estimates suffer\n",
304-
"from the misspecification \u2014 $\\tau$ deflates to -5.38 because the\n",
304+
"from the misspecification \u2014 $\\tau$ deflates to ~-5.4 because the\n",
305305
"\"clean control\" arm now contains genuinely-affected units, and the\n",
306-
"spillover coefficient $\\delta_1$ attenuates to -2.59 because the\n",
306+
"spillover coefficient $\\delta_1$ attenuates to ~-2.6 because the\n",
307307
"$S = 1$ ring averages 50-78 km exposure into the cleaner\n",
308308
"$S = 0$ comparison. This is the registry-documented failure mode for\n",
309-
"undershooting `d_bar`.\n",
309+
"undershooting `d_bar`. (The exact values are quoted to one decimal\n",
310+
"here rather than two because the `d_bar = 50` design is borderline-\n",
311+
"rank-deficient \u2014 the two-decimal value can shift slightly across BLAS\n",
312+
"paths even at the same locked seed.)\n",
310313
"\n",
311314
"At `d_bar = 100` km the ring covers the entire DGP near-control band\n",
312315
"(0.1\u00b0-0.7\u00b0 latitude \u2248 11-78 km). $\\tau$ recovers to -7.34 and\n",
@@ -327,7 +330,7 @@
327330
},
328331
{
329332
"cell_type": "markdown",
330-
"id": "3234a2c8",
333+
"id": "626ba92d",
331334
"metadata": {},
332335
"source": [
333336
"## 5. Fit `SpilloverDiD` and interpret\n",
@@ -339,7 +342,7 @@
339342
{
340343
"cell_type": "code",
341344
"execution_count": null,
342-
"id": "9ca37cfd",
345+
"id": "68e05342",
343346
"metadata": {},
344347
"outputs": [],
345348
"source": [
@@ -366,7 +369,7 @@
366369
},
367370
{
368371
"cell_type": "markdown",
369-
"id": "a0d51daa",
372+
"id": "dc82c017",
370373
"metadata": {},
371374
"source": [
372375
"With the spillover term in the regression, `SpilloverDiD` cleanly\n",
@@ -384,7 +387,7 @@
384387
},
385388
{
386389
"cell_type": "markdown",
387-
"id": "b668b174",
390+
"id": "b46af080",
388391
"metadata": {},
389392
"source": [
390393
"## 6. Robust inference with Conley spatial-HAC\n",
@@ -408,7 +411,7 @@
408411
{
409412
"cell_type": "code",
410413
"execution_count": null,
411-
"id": "116b4b3c",
414+
"id": "380eacc7",
412415
"metadata": {},
413416
"outputs": [],
414417
"source": [
@@ -442,7 +445,7 @@
442445
},
443446
{
444447
"cell_type": "markdown",
445-
"id": "7169c550",
448+
"id": "5e96dc31",
446449
"metadata": {},
447450
"source": [
448451
"Point estimates are identical across all three rows \u2014 the variance\n",
@@ -492,7 +495,7 @@
492495
},
493496
{
494497
"cell_type": "markdown",
495-
"id": "4485f89f",
498+
"id": "8bf9c211",
496499
"metadata": {},
497500
"source": [
498501
"## 7. Practitioner takeaways and where to go next\n",

tests/test_t23_spillover_tva_drift.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -241,21 +241,32 @@ def test_naive_twfe_understates_tau_total(naive_fit):
241241

242242

243243
def test_naive_att_endpoint_matches_quoted(naive_fit):
244-
"""§3 quoted endpoint: round-to-1 pin (looser than round-to-2 for BLAS safety)."""
245-
assert round(naive_fit.att, 1) == -4.3
244+
"""§3 quoted endpoint: 2-decimal pin matching the published `-4.29`
245+
in the notebook, README, and CHANGELOG. The well-conditioned naive
246+
MultiPeriodDiD fit is stable across BLAS paths to better than 0.005,
247+
so 2-decimal pinning is safe (in contrast to the borderline-rank-
248+
deficient `rings=[0,50]` sensitivity point, which we keep at
249+
round-to-1)."""
250+
assert round(naive_fit.att, 2) == -4.29
246251

247252

248253
def test_spillover_did_recovers_tau_total(spillover_fit):
249-
"""§5 quoted: SpilloverDiD tau_total ≈ -7.34 ± 0.12, recovers true -7.4."""
254+
"""§5 quoted: SpilloverDiD tau_total = -7.34, recovers true -7.4
255+
(within 0.5 tolerance bound). Endpoint pinned to 2 decimals
256+
matching the published `-7.34` in the notebook, README, and
257+
CHANGELOG — well-conditioned fit, BLAS-stable at 2 decimals."""
250258
assert abs(spillover_fit.att - TAU_TOTAL) < 0.5
251-
assert round(spillover_fit.att, 1) == -7.3
259+
assert round(spillover_fit.att, 2) == -7.34
252260

253261

254262
def test_spillover_did_recovers_delta_1(spillover_fit):
255-
"""§5 quoted: SpilloverDiD delta_1 ≈ -4.53 ± 0.07, recovers true -4.5."""
263+
"""§5 quoted: SpilloverDiD delta_1 = -4.53, recovers true -4.5
264+
(within 0.5 tolerance bound). Endpoint pinned to 2 decimals
265+
matching the published `-4.53` in the notebook, README, and
266+
CHANGELOG — well-conditioned fit, BLAS-stable at 2 decimals."""
256267
delta_1 = float(spillover_fit.spillover_effects.iloc[0]["coef"])
257268
assert abs(delta_1 - DELTA_1) < 0.5
258-
assert round(delta_1, 1) == -4.5
269+
assert round(delta_1, 2) == -4.53
259270

260271

261272
def test_rings_sensitivity_grid_endpoints(panel):

0 commit comments

Comments
 (0)