Skip to content

Commit 85b76f4

Browse files
igerberclaude
andcommitted
Fix DGP homogeneity misstatement: disable dynamic effects in tutorial
The tutorial describes a homogeneous DGP with "True effect = 2.0" but generate_staggered_data() defaults to dynamic_effects=True, making effects grow with event time. Add dynamic_effects=False to both calls so the DGP matches the prose. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent dba7d2f commit 85b76f4

1 file changed

Lines changed: 98 additions & 8 deletions

File tree

docs/tutorials/15_efficient_did.ipynb

Lines changed: 98 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,21 @@
6363
"cell_type": "markdown",
6464
"id": "4d734cd9",
6565
"metadata": {},
66-
"source": "## What Makes EDiD Different?\n\nConsider a staggered adoption design with cohorts treated at periods 3, 5, and 7, plus a never-treated group. To estimate ATT(g=5, t=6), **Callaway-Sant'Anna** uses a single 2x2 comparison:\n\n> *Compare the outcome change from period 4 to 6 for cohort 5 versus the never-treated group.*\n\nBut under **PT-All** (parallel trends across all pre-treatment periods), there are *additional* valid comparisons. Cohort 7 is also untreated at period 6, so it can serve as a comparison group too. And periods 2 and 3 can serve as additional valid baselines beyond CS's default period 4. (Period 1 is excluded --- it is the fixed $Y_1$ reference used in every comparison's differencing, so using it as a baseline adds no information.)\n\nEach of these comparisons provides an unbiased estimate of ATT(g=5, t=6), but with different variances. **EDiD finds the optimal linear combination** --- the one that minimizes variance --- by computing the inverse covariance matrix of these \"generated outcomes\" (the paper calls this $\\Omega^*$).\n\nThe result: **matching post-treatment ATT(g,t) with CS under PT-Post**, but **tighter standard errors under PT-All** because EDiD exploits the overidentification.\n\n> **Key equation (for the curious):** The efficient weight vector is $w^* = \\frac{\\mathbf{1}' \\Omega^{*-1}}{\\mathbf{1}' \\Omega^{*-1} \\mathbf{1}}$, where $\\Omega^*$ is the covariance matrix of the generated outcomes across all valid (comparison group, baseline) pairs. This is the classic GLS optimal weighting. See REGISTRY.md or the paper for full derivations."
66+
"source": [
67+
"## What Makes EDiD Different?\n",
68+
"\n",
69+
"Consider a staggered adoption design with cohorts treated at periods 3, 5, and 7, plus a never-treated group. To estimate ATT(g=5, t=6), **Callaway-Sant'Anna** uses a single 2x2 comparison:\n",
70+
"\n",
71+
"> *Compare the outcome change from period 4 to 6 for cohort 5 versus the never-treated group.*\n",
72+
"\n",
73+
"But under **PT-All** (parallel trends across all pre-treatment periods), there are *additional* valid comparisons. Cohort 7 is also untreated at period 6, so it can serve as a comparison group too. And periods 2 and 3 can serve as additional valid baselines beyond CS's default period 4. (Period 1 is excluded --- it is the fixed $Y_1$ reference used in every comparison's differencing, so using it as a baseline adds no information.)\n",
74+
"\n",
75+
"Each of these comparisons provides an unbiased estimate of ATT(g=5, t=6), but with different variances. **EDiD finds the optimal linear combination** --- the one that minimizes variance --- by computing the inverse covariance matrix of these \"generated outcomes\" (the paper calls this $\\Omega^*$).\n",
76+
"\n",
77+
"The result: **matching post-treatment ATT(g,t) with CS under PT-Post**, but **tighter standard errors under PT-All** because EDiD exploits the overidentification.\n",
78+
"\n",
79+
"> **Key equation (for the curious):** The efficient weight vector is $w^* = \\frac{\\mathbf{1}' \\Omega^{*-1}}{\\mathbf{1}' \\Omega^{*-1} \\mathbf{1}}$, where $\\Omega^*$ is the covariance matrix of the generated outcomes across all valid (comparison group, baseline) pairs. This is the classic GLS optimal weighting. See REGISTRY.md or the paper for full derivations."
80+
]
6781
},
6882
{
6983
"cell_type": "markdown",
@@ -82,7 +96,8 @@
8296
"metadata": {},
8397
"outputs": [],
8498
"source": [
85-
"data = generate_staggered_data(n_units=300, n_periods=10, treatment_effect=2.0, seed=42)\n",
99+
"data = generate_staggered_data(n_units=300, n_periods=10, treatment_effect=2.0,\n",
100+
" dynamic_effects=False, seed=42)\n",
86101
"\n",
87102
"print(f\"Shape: {data.shape}\")\n",
88103
"print(f\"Cohorts: {sorted(data['first_treat'].unique())}\")\n",
@@ -141,15 +156,50 @@
141156
"cell_type": "markdown",
142157
"id": "e1ad14f5",
143158
"metadata": {},
144-
"source": "## PT-All vs PT-Post\n\nEDiD supports two parallel trends assumptions:\n\n- **PT-All** (`pt_assumption=\"all\"`): Parallel trends holds across *all* pre-treatment periods. The model is overidentified --- more valid comparisons exist than needed --- and EDiD exploits this for tighter SEs.\n- **PT-Post** (`pt_assumption=\"post\"`): Parallel trends holds only from `g-1` onward (the weaker, standard assumption). EDiD uses a single baseline (`g-1`) per cohort, matching `CallawaySantAnna(control_group='never_treated')` for post-treatment ATT(g,t). Pre-treatment diagnostics may differ from CS's default `base_period='varying'`.\n\nPT-All is the default because it delivers efficiency gains when the assumption holds. Use PT-Post if you're concerned about violations in early pre-treatment periods."
159+
"source": [
160+
"## PT-All vs PT-Post\n",
161+
"\n",
162+
"EDiD supports two parallel trends assumptions:\n",
163+
"\n",
164+
"- **PT-All** (`pt_assumption=\"all\"`): Parallel trends holds across *all* pre-treatment periods. The model is overidentified --- more valid comparisons exist than needed --- and EDiD exploits this for tighter SEs.\n",
165+
"- **PT-Post** (`pt_assumption=\"post\"`): Parallel trends holds only from `g-1` onward (the weaker, standard assumption). EDiD uses a single baseline (`g-1`) per cohort, matching `CallawaySantAnna(control_group='never_treated')` for post-treatment ATT(g,t). Pre-treatment diagnostics may differ from CS's default `base_period='varying'`.\n",
166+
"\n",
167+
"PT-All is the default because it delivers efficiency gains when the assumption holds. Use PT-Post if you're concerned about violations in early pre-treatment periods."
168+
]
145169
},
146170
{
147171
"cell_type": "code",
148172
"execution_count": null,
149173
"id": "35f70199",
150174
"metadata": {},
151175
"outputs": [],
152-
"source": "# Fit under both assumptions\nresults_all = EfficientDiD(pt_assumption=\"all\").fit(\n data, outcome='outcome', unit='unit', time='period',\n first_treat='first_treat', aggregate='all')\n\nresults_post = EfficientDiD(pt_assumption=\"post\").fit(\n data, outcome='outcome', unit='unit', time='period',\n first_treat='first_treat', aggregate='all')\n\n# Compare with Callaway-Sant'Anna\nresults_cs = CallawaySantAnna().fit(\n data, outcome='outcome', unit='unit', time='period',\n first_treat='first_treat')\n\nprint(\"PT-All vs PT-Post vs Callaway-Sant'Anna\")\nprint(\"=\" * 65)\nprint(f\"{'Estimator':<25} {'ATT':>10} {'SE':>10} {'CI Width':>12}\")\nprint(\"-\" * 65)\nfor name, r in [(\"EDiD (PT-All)\", results_all),\n (\"EDiD (PT-Post)\", results_post),\n (\"CallawaySantAnna\", results_cs)]:\n ci_width = r.overall_conf_int[1] - r.overall_conf_int[0]\n print(f\"{name:<25} {r.overall_att:>10.4f} {r.overall_se:>10.4f} {ci_width:>12.4f}\")\nprint()\nprint(\"PT-Post and CS produce identical post-treatment ATTs.\")"
176+
"source": [
177+
"# Fit under both assumptions\n",
178+
"results_all = EfficientDiD(pt_assumption=\"all\").fit(\n",
179+
" data, outcome='outcome', unit='unit', time='period',\n",
180+
" first_treat='first_treat', aggregate='all')\n",
181+
"\n",
182+
"results_post = EfficientDiD(pt_assumption=\"post\").fit(\n",
183+
" data, outcome='outcome', unit='unit', time='period',\n",
184+
" first_treat='first_treat', aggregate='all')\n",
185+
"\n",
186+
"# Compare with Callaway-Sant'Anna\n",
187+
"results_cs = CallawaySantAnna().fit(\n",
188+
" data, outcome='outcome', unit='unit', time='period',\n",
189+
" first_treat='first_treat')\n",
190+
"\n",
191+
"print(\"PT-All vs PT-Post vs Callaway-Sant'Anna\")\n",
192+
"print(\"=\" * 65)\n",
193+
"print(f\"{'Estimator':<25} {'ATT':>10} {'SE':>10} {'CI Width':>12}\")\n",
194+
"print(\"-\" * 65)\n",
195+
"for name, r in [(\"EDiD (PT-All)\", results_all),\n",
196+
" (\"EDiD (PT-Post)\", results_post),\n",
197+
" (\"CallawaySantAnna\", results_cs)]:\n",
198+
" ci_width = r.overall_conf_int[1] - r.overall_conf_int[0]\n",
199+
" print(f\"{name:<25} {r.overall_att:>10.4f} {r.overall_se:>10.4f} {ci_width:>12.4f}\")\n",
200+
"print()\n",
201+
"print(\"PT-Post and CS produce identical post-treatment ATTs.\")"
202+
]
153203
},
154204
{
155205
"cell_type": "markdown",
@@ -174,7 +224,8 @@
174224
"\n",
175225
"for seed in range(n_seeds):\n",
176226
" sim_data = generate_staggered_data(n_units=200, n_periods=8,\n",
177-
" treatment_effect=2.0, seed=seed)\n",
227+
" treatment_effect=2.0,\n",
228+
" dynamic_effects=False, seed=seed)\n",
178229
" r_edid = EfficientDiD(pt_assumption=\"all\").fit(\n",
179230
" sim_data, outcome='outcome', unit='unit', time='period',\n",
180231
" first_treat='first_treat')\n",
@@ -274,7 +325,16 @@
274325
"cell_type": "markdown",
275326
"id": "a89342da",
276327
"metadata": {},
277-
"source": "## Bootstrap Inference\n\nEDiD supports multiplier bootstrap for inference. The bootstrap perturbs the influence function values with random weights to obtain bootstrap distributions of all parameters.\n\nThree weight distributions are available:\n- **Rademacher** (default): $\\pm 1$ with equal probability --- standard choice, works well in most settings\n- **Mammen**: Two-point distribution that matches third moments\n- **Webb**: Six-point distribution with wider support"
328+
"source": [
329+
"## Bootstrap Inference\n",
330+
"\n",
331+
"EDiD supports multiplier bootstrap for inference. The bootstrap perturbs the influence function values with random weights to obtain bootstrap distributions of all parameters.\n",
332+
"\n",
333+
"Three weight distributions are available:\n",
334+
"- **Rademacher** (default): $\\pm 1$ with equal probability --- standard choice, works well in most settings\n",
335+
"- **Mammen**: Two-point distribution that matches third moments\n",
336+
"- **Webb**: Six-point distribution with wider support"
337+
]
278338
},
279339
{
280340
"cell_type": "code",
@@ -485,7 +545,37 @@
485545
"cell_type": "markdown",
486546
"id": "ef99ee47",
487547
"metadata": {},
488-
"source": "## Summary\n\n**Key takeaways:**\n\n1. EDiD achieves the **semiparametric efficiency bound** for ATT estimation in staggered designs\n2. Under **PT-All**, EDiD exploits overidentification for tighter SEs than CS\n3. Under **PT-Post**, EDiD matches CS for post-treatment ATT(g,t); pre-treatment diagnostics use a fixed baseline and may differ from CS's default varying baseline\n4. The efficiency gain comes from optimally weighting across all valid (comparison group, baseline) pairs\n5. **Event study** and **group** aggregations work just like CS\n6. **Multiplier bootstrap** provides robust inference with Rademacher, Mammen, or Webb weights\n7. **Condition numbers** flag potentially unstable weight matrices\n8. **Anticipation** shifts the effective treatment boundary for pre-treatment effects\n9. Phase 1 is **no-covariates only** --- Phase 2 will add covariate support\n10. When in doubt, run both EDiD and CS --- if ATTs agree, report EDiD for tighter CIs\n\n**Parameter reference:**\n\n| Parameter | Default | Description |\n|-----------|---------|-------------|\n| `pt_assumption` | `\"all\"` | `\"all\"` (overidentified) or `\"post\"` (just-identified, matches CS post-treatment ATT) |\n| `alpha` | `0.05` | Significance level |\n| `n_bootstrap` | `0` | Number of bootstrap iterations (0 = analytical only) |\n| `bootstrap_weights` | `\"rademacher\"` | Bootstrap weight distribution: `\"rademacher\"`, `\"mammen\"`, `\"webb\"` |\n| `seed` | `None` | Random seed for reproducibility |\n| `anticipation` | `0` | Anticipation periods |\n\n**Reference:** Chen, X., Sant'Anna, P. H. C., & Xie, H. (2025). Efficient Difference-in-Differences and Event Study Estimators.\n\n*See also: [Choosing an Estimator](../choosing_estimator.rst) for guidance on when to use EDiD vs other estimators.*"
548+
"source": [
549+
"## Summary\n",
550+
"\n",
551+
"**Key takeaways:**\n",
552+
"\n",
553+
"1. EDiD achieves the **semiparametric efficiency bound** for ATT estimation in staggered designs\n",
554+
"2. Under **PT-All**, EDiD exploits overidentification for tighter SEs than CS\n",
555+
"3. Under **PT-Post**, EDiD matches CS for post-treatment ATT(g,t); pre-treatment diagnostics use a fixed baseline and may differ from CS's default varying baseline\n",
556+
"4. The efficiency gain comes from optimally weighting across all valid (comparison group, baseline) pairs\n",
557+
"5. **Event study** and **group** aggregations work just like CS\n",
558+
"6. **Multiplier bootstrap** provides robust inference with Rademacher, Mammen, or Webb weights\n",
559+
"7. **Condition numbers** flag potentially unstable weight matrices\n",
560+
"8. **Anticipation** shifts the effective treatment boundary for pre-treatment effects\n",
561+
"9. Phase 1 is **no-covariates only** --- Phase 2 will add covariate support\n",
562+
"10. When in doubt, run both EDiD and CS --- if ATTs agree, report EDiD for tighter CIs\n",
563+
"\n",
564+
"**Parameter reference:**\n",
565+
"\n",
566+
"| Parameter | Default | Description |\n",
567+
"|-----------|---------|-------------|\n",
568+
"| `pt_assumption` | `\"all\"` | `\"all\"` (overidentified) or `\"post\"` (just-identified, matches CS post-treatment ATT) |\n",
569+
"| `alpha` | `0.05` | Significance level |\n",
570+
"| `n_bootstrap` | `0` | Number of bootstrap iterations (0 = analytical only) |\n",
571+
"| `bootstrap_weights` | `\"rademacher\"` | Bootstrap weight distribution: `\"rademacher\"`, `\"mammen\"`, `\"webb\"` |\n",
572+
"| `seed` | `None` | Random seed for reproducibility |\n",
573+
"| `anticipation` | `0` | Anticipation periods |\n",
574+
"\n",
575+
"**Reference:** Chen, X., Sant'Anna, P. H. C., & Xie, H. (2025). Efficient Difference-in-Differences and Event Study Estimators.\n",
576+
"\n",
577+
"*See also: [Choosing an Estimator](../choosing_estimator.rst) for guidance on when to use EDiD vs other estimators.*"
578+
]
489579
}
490580
],
491581
"metadata": {
@@ -495,4 +585,4 @@
495585
},
496586
"nbformat": 4,
497587
"nbformat_minor": 5
498-
}
588+
}

0 commit comments

Comments
 (0)