Skip to content

Commit d8d338e

Browse files
authored
Merge pull request #42 from igerber/claude/implement-sun-abraham-estimator-GnbVC
2 parents 6de1bff + 968fe76 commit d8d338e

10 files changed

Lines changed: 2283 additions & 37 deletions

File tree

File renamed without changes.

CLAUDE.md

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ mypy diff_diff
5353
- `GroupTimeEffect` - Container for individual group-time effects
5454
- Multiplier bootstrap with Rademacher, Mammen, or Webb weights
5555

56+
- **`diff_diff/sun_abraham.py`** - Sun-Abraham interaction-weighted estimator:
57+
- `SunAbraham` - Sun & Abraham (2021) estimator using saturated regression
58+
- `SunAbrahamResults` - Results with event study effects and cohort weights
59+
- `SABootstrapResults` - Bootstrap inference results
60+
- Alternative to Callaway-Sant'Anna with different weighting scheme
61+
- Useful robustness check when both estimators agree
62+
5663
- **`diff_diff/bacon.py`** - Goodman-Bacon decomposition for TWFE diagnostics:
5764
- `BaconDecomposition` - Decompose TWFE into weighted 2x2 comparisons (Goodman-Bacon 2021)
5865
- `BaconDecompositionResults` - Results with comparison weights and estimates by type
@@ -71,7 +78,7 @@ mypy diff_diff
7178
- `plot_honest_event_study` - Event study with honest confidence intervals
7279
- `plot_bacon` - Bacon decomposition scatter/bar plots (weights vs estimates by comparison type)
7380
- `plot_power_curve` - Power curve visualization (power vs effect size or sample size)
74-
- Works with MultiPeriodDiD, CallawaySantAnna, HonestDiD, BaconDecomposition, PowerAnalysis, or DataFrames
81+
- Works with MultiPeriodDiD, CallawaySantAnna, SunAbraham, HonestDiD, BaconDecomposition, PowerAnalysis, or DataFrames
7582

7683
- **`diff_diff/utils.py`** - Statistical utilities:
7784
- Robust/cluster standard errors (`compute_robust_se`)
@@ -136,6 +143,7 @@ mypy diff_diff
136143
Tests mirror the source modules:
137144
- `tests/test_estimators.py` - Tests for DifferenceInDifferences, TWFE, MultiPeriodDiD, SyntheticDiD
138145
- `tests/test_staggered.py` - Tests for CallawaySantAnna
146+
- `tests/test_sun_abraham.py` - Tests for SunAbraham interaction-weighted estimator
139147
- `tests/test_bacon.py` - Tests for Goodman-Bacon decomposition
140148
- `tests/test_utils.py` - Tests for parallel trends, robust SE, synthetic weights
141149
- `tests/test_diagnostics.py` - Tests for placebo tests
@@ -148,3 +156,55 @@ Tests mirror the source modules:
148156
### Dependencies
149157

150158
Core dependencies are numpy, pandas, and scipy only (no statsmodels). The library implements its own OLS, robust standard errors, and inference.
159+
160+
## Documentation Requirements
161+
162+
When implementing new functionality, **always include accompanying documentation updates**:
163+
164+
### For New Estimators or Major Features
165+
166+
1. **README.md** - Add:
167+
- Feature mention in the features list
168+
- Full usage section with code examples
169+
- Parameter documentation table
170+
- API reference section (constructor params, fit() params, results attributes/methods)
171+
- Scholarly references if applicable
172+
173+
2. **docs/api/*.rst** - Add:
174+
- RST documentation with `autoclass` directives
175+
- Method summaries
176+
- References to academic papers
177+
178+
3. **docs/tutorials/*.ipynb** - Update relevant tutorial or create new one:
179+
- Working code examples
180+
- Explanation of when/why to use the feature
181+
- Comparison with related functionality
182+
183+
4. **CLAUDE.md** - Update:
184+
- Module structure section
185+
- Test structure section
186+
- Any relevant design patterns
187+
188+
5. **ROADMAP.md** - Update:
189+
- Move implemented features from planned to current status
190+
- Update version numbers
191+
192+
### For Bug Fixes or Minor Enhancements
193+
194+
- Update relevant docstrings
195+
- Add/update tests
196+
- Update CHANGELOG.md (if exists)
197+
198+
### Scholarly References
199+
200+
For methods based on academic papers, always include:
201+
- Full citation in README.md references section
202+
- Reference in RST docs with paper details
203+
- Citation in tutorial summary
204+
205+
Example format:
206+
```
207+
Sun, L., & Abraham, S. (2021). Estimating dynamic treatment effects in
208+
event studies with heterogeneous treatment effects. *Journal of Econometrics*,
209+
225(2), 175-199.
210+
```

README.md

Lines changed: 170 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ Signif. codes: '***' 0.001, '**' 0.01, '*' 0.05, '.' 0.1
7070
- **Wild cluster bootstrap**: Valid inference with few clusters (<50) using Rademacher, Webb, or Mammen weights
7171
- **Panel data support**: Two-way fixed effects estimator for panel designs
7272
- **Multi-period analysis**: Event-study style DiD with period-specific treatment effects
73-
- **Staggered adoption**: Callaway-Sant'Anna (2021) estimator for heterogeneous treatment timing
73+
- **Staggered adoption**: Callaway-Sant'Anna (2021) and Sun-Abraham (2021) estimators for heterogeneous treatment timing
7474
- **Synthetic DiD**: Combined DiD with synthetic control for improved robustness
7575
- **Event study plots**: Publication-ready visualization of treatment effects
7676
- **Parallel trends testing**: Multiple methods including equivalence tests
@@ -87,7 +87,7 @@ We provide Jupyter notebook tutorials in `docs/tutorials/`:
8787
| Notebook | Description |
8888
|----------|-------------|
8989
| `01_basic_did.ipynb` | Basic 2x2 DiD, formula interface, covariates, fixed effects, cluster-robust SE, wild bootstrap |
90-
| `02_staggered_did.ipynb` | Staggered adoption with Callaway-Sant'Anna, group-time effects, aggregation methods, Bacon decomposition |
90+
| `02_staggered_did.ipynb` | Staggered adoption with Callaway-Sant'Anna and Sun-Abraham, group-time effects, aggregation methods, Bacon decomposition |
9191
| `03_synthetic_did.ipynb` | Synthetic DiD, unit/time weights, inference methods, regularization |
9292
| `04_parallel_trends.ipynb` | Testing parallel trends, equivalence tests, placebo tests, diagnostics |
9393
| `05_honest_did.ipynb` | Honest DiD sensitivity analysis, bounds, breakdown values, visualization |
@@ -762,12 +762,115 @@ results = cs.fit(
762762
)
763763
```
764764

765+
### Sun-Abraham Interaction-Weighted Estimator
766+
767+
The Sun-Abraham (2021) estimator provides an alternative to Callaway-Sant'Anna using an interaction-weighted (IW) regression approach. Running both estimators serves as a useful robustness check—when they agree, results are more credible.
768+
769+
```python
770+
from diff_diff import SunAbraham
771+
772+
# Basic usage
773+
sa = SunAbraham()
774+
results = sa.fit(
775+
panel_data,
776+
outcome='sales',
777+
unit='firm_id',
778+
time='year',
779+
first_treat='first_treat' # 0 for never-treated, else first treatment year
780+
)
781+
782+
# View results
783+
results.print_summary()
784+
785+
# Event study effects (by relative time to treatment)
786+
for rel_time, effect in results.event_study_effects.items():
787+
print(f"e={rel_time}: {effect['effect']:.3f} (SE: {effect['se']:.3f})")
788+
789+
# Overall ATT
790+
print(f"Overall ATT: {results.overall_att:.3f} (SE: {results.overall_se:.3f})")
791+
792+
# Cohort weights (how each cohort contributes to each event-time estimate)
793+
for rel_time, weights in results.cohort_weights.items():
794+
print(f"e={rel_time}: {weights}")
795+
```
796+
797+
**Parameters:**
798+
799+
```python
800+
SunAbraham(
801+
control_group='never_treated', # or 'not_yet_treated'
802+
anticipation=0, # Periods before treatment with effects
803+
alpha=0.05, # Significance level
804+
cluster=None, # Column for cluster SEs
805+
n_bootstrap=0, # Bootstrap iterations (0 = analytical SEs)
806+
bootstrap_weights='rademacher', # 'rademacher', 'mammen', or 'webb'
807+
seed=None # Random seed
808+
)
809+
```
810+
811+
**Bootstrap inference:**
812+
813+
```python
814+
# Bootstrap inference with 999 iterations
815+
sa = SunAbraham(
816+
n_bootstrap=999,
817+
bootstrap_weights='rademacher',
818+
seed=42
819+
)
820+
results = sa.fit(
821+
data,
822+
outcome='sales',
823+
unit='firm_id',
824+
time='year',
825+
first_treat='first_treat'
826+
)
827+
828+
# Access bootstrap results
829+
print(f"Overall ATT: {results.overall_att:.3f}")
830+
print(f"Bootstrap SE: {results.bootstrap_results.overall_att_se:.3f}")
831+
print(f"Bootstrap 95% CI: {results.bootstrap_results.overall_att_ci}")
832+
print(f"Bootstrap p-value: {results.bootstrap_results.overall_att_p_value:.4f}")
833+
```
834+
835+
**When to use Sun-Abraham vs Callaway-Sant'Anna:**
836+
837+
| Aspect | Sun-Abraham | Callaway-Sant'Anna |
838+
|--------|-------------|-------------------|
839+
| Approach | Interaction-weighted regression | 2x2 DiD aggregation |
840+
| Efficiency | More efficient under homogeneous effects | More robust to heterogeneity |
841+
| Weighting | Weights by cohort share at each relative time | Weights by sample size |
842+
| Use case | Robustness check, regression-based inference | Primary staggered DiD estimator |
843+
844+
**Both estimators should give similar results when:**
845+
- Treatment effects are relatively homogeneous across cohorts
846+
- Parallel trends holds
847+
848+
**Running both as robustness check:**
849+
850+
```python
851+
from diff_diff import CallawaySantAnna, SunAbraham
852+
853+
# Callaway-Sant'Anna
854+
cs = CallawaySantAnna()
855+
cs_results = cs.fit(data, outcome='y', unit='unit', time='time', first_treat='first_treat')
856+
857+
# Sun-Abraham
858+
sa = SunAbraham()
859+
sa_results = sa.fit(data, outcome='y', unit='unit', time='time', first_treat='first_treat')
860+
861+
# Compare
862+
print(f"Callaway-Sant'Anna ATT: {cs_results.overall_att:.3f}")
863+
print(f"Sun-Abraham ATT: {sa_results.overall_att:.3f}")
864+
865+
# If results differ substantially, investigate heterogeneity
866+
```
867+
765868
### Event Study Visualization
766869

767870
Create publication-ready event study plots:
768871

769872
```python
770-
from diff_diff import plot_event_study, MultiPeriodDiD, CallawaySantAnna
873+
from diff_diff import plot_event_study, MultiPeriodDiD, CallawaySantAnna, SunAbraham
771874

772875
# From MultiPeriodDiD
773876
did = MultiPeriodDiD()
@@ -779,7 +882,13 @@ plot_event_study(results, title="Treatment Effects Over Time")
779882
cs = CallawaySantAnna()
780883
results = cs.fit(data, outcome='y', unit='unit', time='period',
781884
first_treat='first_treat', aggregate='event_study')
782-
plot_event_study(results, title="Staggered DiD Event Study")
885+
plot_event_study(results, title="Staggered DiD Event Study (CS)")
886+
887+
# From SunAbraham
888+
sa = SunAbraham()
889+
results = sa.fit(data, outcome='y', unit='unit', time='period',
890+
first_treat='first_treat')
891+
plot_event_study(results, title="Staggered DiD Event Study (SA)")
783892

784893
# From a DataFrame
785894
df = pd.DataFrame({
@@ -1410,6 +1519,63 @@ SyntheticDiD(
14101519
| `get_unit_weights_df()` | Get unit weights as DataFrame |
14111520
| `get_time_weights_df()` | Get time weights as DataFrame |
14121521

1522+
### SunAbraham
1523+
1524+
```python
1525+
SunAbraham(
1526+
control_group='never_treated', # or 'not_yet_treated'
1527+
anticipation=0, # Periods of anticipation effects
1528+
alpha=0.05, # Significance level for CIs
1529+
cluster=None, # Column for cluster-robust SEs
1530+
n_bootstrap=0, # Bootstrap iterations (0 = analytical SEs)
1531+
bootstrap_weights='rademacher', # 'rademacher', 'mammen', or 'webb'
1532+
seed=None # Random seed
1533+
)
1534+
```
1535+
1536+
**fit() Parameters:**
1537+
1538+
| Parameter | Type | Description |
1539+
|-----------|------|-------------|
1540+
| `data` | DataFrame | Panel data |
1541+
| `outcome` | str | Outcome variable column name |
1542+
| `unit` | str | Unit identifier column |
1543+
| `time` | str | Time period column |
1544+
| `first_treat` | str | Column with first treatment period (0 for never-treated) |
1545+
| `covariates` | list | Covariate column names |
1546+
| `min_pre_periods` | int | Minimum pre-treatment periods to include |
1547+
| `min_post_periods` | int | Minimum post-treatment periods to include |
1548+
1549+
### SunAbrahamResults
1550+
1551+
**Attributes:**
1552+
1553+
| Attribute | Description |
1554+
|-----------|-------------|
1555+
| `event_study_effects` | Dict mapping relative time to effect info |
1556+
| `overall_att` | Overall average treatment effect |
1557+
| `overall_se` | Standard error of overall ATT |
1558+
| `overall_t_stat` | T-statistic for overall ATT |
1559+
| `overall_p_value` | P-value for overall ATT |
1560+
| `overall_conf_int` | Confidence interval for overall ATT |
1561+
| `cohort_weights` | Dict mapping relative time to cohort weights |
1562+
| `groups` | List of treatment cohorts |
1563+
| `time_periods` | List of all time periods |
1564+
| `n_obs` | Total number of observations |
1565+
| `n_treated_units` | Number of ever-treated units |
1566+
| `n_control_units` | Number of never-treated units |
1567+
| `is_significant` | Boolean for significance at alpha |
1568+
| `significance_stars` | String of significance stars |
1569+
| `bootstrap_results` | SABootstrapResults (if bootstrap enabled) |
1570+
1571+
**Methods:**
1572+
1573+
| Method | Description |
1574+
|--------|-------------|
1575+
| `summary(alpha)` | Get formatted summary string |
1576+
| `print_summary(alpha)` | Print summary to stdout |
1577+
| `to_dataframe(level)` | Convert to DataFrame ('event_study' or 'cohort') |
1578+
14131579
### HonestDiD
14141580

14151581
```python

ROADMAP.md

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,22 @@ For past changes and release history, see [CHANGELOG.md](CHANGELOG.md).
66

77
---
88

9-
## Current Status (v1.0.2)
9+
## Current Status (v1.1.0)
1010

1111
diff-diff is a **production-ready** DiD library with feature parity with R's `did` + `HonestDiD` ecosystem for core DiD analysis:
1212

13-
- **Core estimators**: Basic DiD, TWFE, MultiPeriod, Callaway-Sant'Anna, Synthetic DiD
13+
- **Core estimators**: Basic DiD, TWFE, MultiPeriod, Callaway-Sant'Anna, Sun-Abraham, Synthetic DiD
1414
- **Valid inference**: Robust SEs, cluster SEs, wild bootstrap, multiplier bootstrap
1515
- **Assumption diagnostics**: Parallel trends tests, placebo tests, Goodman-Bacon decomposition
1616
- **Sensitivity analysis**: Honest DiD (Rambachan-Roth)
1717
- **Study design**: Power analysis tools
1818

1919
---
2020

21-
## Near-Term Enhancements (v1.1–v1.2)
21+
## Near-Term Enhancements (v1.2)
2222

2323
High-value additions building on our existing foundation.
2424

25-
### Sun-Abraham Estimator
26-
27-
Interaction-weighted estimator providing an alternative to Callaway-Sant'Anna. Many practitioners run both as a robustness check.
28-
29-
- Event-study coefficients via saturated regression with cohort-time interactions
30-
- Different weighting scheme than CS; can give different results under heterogeneous effects
31-
- Useful robustness check when CS and SA agree
32-
33-
**Reference**: Sun & Abraham (2021). *Journal of Econometrics*.
34-
3525
### Borusyak-Jaravel-Spiess Imputation Estimator
3626

3727
More efficient than Callaway-Sant'Anna when treatment effects are homogeneous across groups/time. Uses imputation rather than aggregation.

diff_diff/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@
6969
CSBootstrapResults,
7070
GroupTimeEffect,
7171
)
72+
from diff_diff.sun_abraham import (
73+
SABootstrapResults,
74+
SunAbraham,
75+
SunAbrahamResults,
76+
)
7277
from diff_diff.utils import (
7378
WildBootstrapResults,
7479
check_parallel_trends,
@@ -85,14 +90,15 @@
8590
plot_sensitivity,
8691
)
8792

88-
__version__ = "1.0.2"
93+
__version__ = "1.1.0"
8994
__all__ = [
9095
# Estimators
9196
"DifferenceInDifferences",
9297
"TwoWayFixedEffects",
9398
"MultiPeriodDiD",
9499
"SyntheticDiD",
95100
"CallawaySantAnna",
101+
"SunAbraham",
96102
# Bacon Decomposition
97103
"BaconDecomposition",
98104
"BaconDecompositionResults",
@@ -107,6 +113,8 @@
107113
"CallawaySantAnnaResults",
108114
"CSBootstrapResults",
109115
"GroupTimeEffect",
116+
"SunAbrahamResults",
117+
"SABootstrapResults",
110118
# Visualization
111119
"plot_event_study",
112120
"plot_group_effects",

0 commit comments

Comments
 (0)