diff-diff 2.7.0__tar.gz → 2.7.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diff_diff-2.7.0/README.md → diff_diff-2.7.2/PKG-INFO +122 -4
- diff_diff-2.7.0/PKG-INFO → diff_diff-2.7.2/README.md +76 -43
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/__init__.py +1 -1
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/_backend.py +16 -16
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/datasets.py +3 -3
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/linalg.py +217 -27
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/staggered.py +306 -266
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/staggered_results.py +89 -68
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/sun_abraham.py +9 -15
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/triple_diff.py +241 -258
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/trop.py +70 -53
- {diff_diff-2.7.0 → diff_diff-2.7.2}/pyproject.toml +22 -5
- {diff_diff-2.7.0 → diff_diff-2.7.2}/rust/Cargo.lock +1 -1
- {diff_diff-2.7.0 → diff_diff-2.7.2}/rust/Cargo.toml +1 -1
- {diff_diff-2.7.0 → diff_diff-2.7.2}/rust/src/lib.rs +4 -4
- {diff_diff-2.7.0 → diff_diff-2.7.2}/rust/src/trop.rs +16 -12
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/bacon.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/bootstrap_utils.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/continuous_did.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/continuous_did_bspline.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/continuous_did_results.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/diagnostics.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/efficient_did.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/efficient_did_bootstrap.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/efficient_did_results.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/efficient_did_weights.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/estimators.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/honest_did.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/imputation.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/imputation_bootstrap.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/imputation_results.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/power.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/prep.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/prep_dgp.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/pretrends.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/results.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/stacked_did.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/stacked_did_results.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/staggered_aggregation.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/staggered_bootstrap.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/synthetic_did.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/trop_results.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/twfe.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/two_stage.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/two_stage_bootstrap.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/two_stage_results.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/utils.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/visualization.py +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/rust/build.rs +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/rust/src/bootstrap.rs +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/rust/src/linalg.rs +0 -0
- {diff_diff-2.7.0 → diff_diff-2.7.2}/rust/src/weights.rs +0 -0
|
@@ -1,5 +1,56 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: diff-diff
|
|
3
|
+
Version: 2.7.2
|
|
4
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
5
|
+
Classifier: Intended Audience :: Science/Research
|
|
6
|
+
Classifier: Operating System :: OS Independent
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering
|
|
16
|
+
Requires-Dist: numpy>=1.20.0
|
|
17
|
+
Requires-Dist: pandas>=1.3.0
|
|
18
|
+
Requires-Dist: scipy>=1.7.0
|
|
19
|
+
Requires-Dist: pytest>=7.0 ; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-xdist>=3.0 ; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest-cov>=4.0 ; extra == 'dev'
|
|
22
|
+
Requires-Dist: black>=23.0 ; extra == 'dev'
|
|
23
|
+
Requires-Dist: ruff>=0.1.0 ; extra == 'dev'
|
|
24
|
+
Requires-Dist: mypy>=1.0 ; extra == 'dev'
|
|
25
|
+
Requires-Dist: maturin>=1.4,<2.0 ; extra == 'dev'
|
|
26
|
+
Requires-Dist: matplotlib>=3.5 ; extra == 'dev'
|
|
27
|
+
Requires-Dist: sphinx>=6.0 ; extra == 'docs'
|
|
28
|
+
Requires-Dist: pydata-sphinx-theme>=0.15 ; extra == 'docs'
|
|
29
|
+
Requires-Dist: sphinxext-opengraph>=0.9 ; extra == 'docs'
|
|
30
|
+
Requires-Dist: sphinx-sitemap>=2.5 ; extra == 'docs'
|
|
31
|
+
Requires-Dist: nbsphinx>=0.9 ; extra == 'docs'
|
|
32
|
+
Requires-Dist: matplotlib>=3.5 ; extra == 'docs'
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Provides-Extra: docs
|
|
35
|
+
Summary: Difference-in-Differences causal inference with sklearn-like API. Callaway-Sant'Anna, Synthetic DiD, Honest DiD, event studies, parallel trends.
|
|
36
|
+
Keywords: causal-inference,difference-in-differences,econometrics,statistics,treatment-effects,event-study,staggered-adoption,parallel-trends,synthetic-control,panel-data,did,twfe,callaway-santanna,honest-did,sensitivity-analysis
|
|
37
|
+
Author: diff-diff contributors
|
|
38
|
+
License-Expression: MIT
|
|
39
|
+
Requires-Python: >=3.9, <3.14
|
|
40
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
41
|
+
Project-URL: Documentation, https://diff-diff.readthedocs.io
|
|
42
|
+
Project-URL: Homepage, https://github.com/igerber/diff-diff
|
|
43
|
+
Project-URL: Issues, https://github.com/igerber/diff-diff/issues
|
|
44
|
+
Project-URL: Repository, https://github.com/igerber/diff-diff
|
|
45
|
+
|
|
1
46
|
# diff-diff
|
|
2
47
|
|
|
48
|
+
[](https://pypi.org/project/diff-diff/)
|
|
49
|
+
[](https://pypi.org/project/diff-diff/)
|
|
50
|
+
[](https://opensource.org/licenses/MIT)
|
|
51
|
+
[](https://pypi.org/project/diff-diff/)
|
|
52
|
+
[](https://diff-diff.readthedocs.io/en/stable/)
|
|
53
|
+
|
|
3
54
|
A Python library for Difference-in-Differences (DiD) causal inference analysis with an sklearn-like API and statsmodels-style outputs.
|
|
4
55
|
|
|
5
56
|
## Installation
|
|
@@ -70,7 +121,7 @@ Signif. codes: '***' 0.001, '**' 0.01, '*' 0.05, '.' 0.1
|
|
|
70
121
|
- **Wild cluster bootstrap**: Valid inference with few clusters (<50) using Rademacher, Webb, or Mammen weights
|
|
71
122
|
- **Panel data support**: Two-way fixed effects estimator for panel designs
|
|
72
123
|
- **Multi-period analysis**: Event-study style DiD with period-specific treatment effects
|
|
73
|
-
- **Staggered adoption**: Callaway-Sant'Anna (2021), Sun-Abraham (2021), Borusyak-Jaravel-Spiess (2024) imputation, Two-Stage DiD (Gardner 2022),
|
|
124
|
+
- **Staggered adoption**: Callaway-Sant'Anna (2021), Sun-Abraham (2021), Borusyak-Jaravel-Spiess (2024) imputation, Two-Stage DiD (Gardner 2022), Stacked DiD (Wing, Freedman & Hollingsworth 2024), and Efficient DiD (Chen, Sant'Anna & Xie 2025) estimators for heterogeneous treatment timing
|
|
74
125
|
- **Triple Difference (DDD)**: Ortiz-Villavicencio & Sant'Anna (2025) estimators with proper covariate handling
|
|
75
126
|
- **Synthetic DiD**: Combined DiD with synthetic control for improved robustness
|
|
76
127
|
- **Triply Robust Panel (TROP)**: Factor-adjusted DiD with synthetic weights (Athey et al. 2025)
|
|
@@ -125,6 +176,7 @@ We provide Jupyter notebook tutorials in `docs/tutorials/`:
|
|
|
125
176
|
| `11_imputation_did.ipynb` | Imputation DiD (Borusyak et al. 2024), pre-trend test, efficiency comparison |
|
|
126
177
|
| `12_two_stage_did.ipynb` | Two-Stage DiD (Gardner 2022), GMM sandwich variance, per-observation effects |
|
|
127
178
|
| `13_stacked_did.ipynb` | Stacked DiD (Wing et al. 2024), Q-weights, sub-experiment inspection, trimming, clean control definitions |
|
|
179
|
+
| `15_efficient_did.ipynb` | Efficient DiD (Chen et al. 2025), optimal weighting, PT-All vs PT-Post, efficiency gains, bootstrap inference |
|
|
128
180
|
|
|
129
181
|
## Data Preparation
|
|
130
182
|
|
|
@@ -1071,6 +1123,56 @@ results = stacked_did(
|
|
|
1071
1123
|
)
|
|
1072
1124
|
```
|
|
1073
1125
|
|
|
1126
|
+
### Efficient DiD (Chen, Sant'Anna & Xie 2025)
|
|
1127
|
+
|
|
1128
|
+
Efficient DiD achieves the semiparametric efficiency bound for ATT estimation in staggered adoption designs. It optimally weights across all valid comparison groups and baselines via the inverse covariance matrix Omega*, producing tighter confidence intervals than standard estimators like Callaway-Sant'Anna when the stronger PT-All assumption holds.
|
|
1129
|
+
|
|
1130
|
+
```python
|
|
1131
|
+
from diff_diff import EfficientDiD, generate_staggered_data
|
|
1132
|
+
|
|
1133
|
+
# Generate sample data
|
|
1134
|
+
data = generate_staggered_data(n_units=300, n_periods=10,
|
|
1135
|
+
cohort_periods=[4, 6, 8], seed=42)
|
|
1136
|
+
|
|
1137
|
+
# Fit with PT-All (overidentified, tighter SEs)
|
|
1138
|
+
edid = EfficientDiD(pt_assumption="all")
|
|
1139
|
+
results = edid.fit(data, outcome='outcome', unit='unit',
|
|
1140
|
+
time='period', first_treat='first_treat',
|
|
1141
|
+
aggregate='all')
|
|
1142
|
+
results.print_summary()
|
|
1143
|
+
|
|
1144
|
+
# PT-Post mode (matches CS for post-treatment effects)
|
|
1145
|
+
edid_post = EfficientDiD(pt_assumption="post")
|
|
1146
|
+
results_post = edid_post.fit(data, outcome='outcome', unit='unit',
|
|
1147
|
+
time='period', first_treat='first_treat')
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
**Parameters:**
|
|
1151
|
+
|
|
1152
|
+
```python
|
|
1153
|
+
EfficientDiD(
|
|
1154
|
+
pt_assumption='all', # 'all' (overidentified) or 'post' (matches CS post-treatment ATT)
|
|
1155
|
+
alpha=0.05, # Significance level
|
|
1156
|
+
n_bootstrap=0, # Bootstrap iterations (0 = analytical only)
|
|
1157
|
+
bootstrap_weights='rademacher', # 'rademacher', 'mammen', or 'webb'
|
|
1158
|
+
seed=None, # Random seed
|
|
1159
|
+
anticipation=0, # Anticipation periods
|
|
1160
|
+
)
|
|
1161
|
+
```
|
|
1162
|
+
|
|
1163
|
+
> **Note:** Phase 1 supports the no-covariates path only. Use CallawaySantAnna with
|
|
1164
|
+
> `estimation_method='dr'` if you need covariate adjustment.
|
|
1165
|
+
|
|
1166
|
+
**When to use Efficient DiD vs Callaway-Sant'Anna:**
|
|
1167
|
+
|
|
1168
|
+
| Aspect | Efficient DiD | Callaway-Sant'Anna |
|
|
1169
|
+
|--------|--------------|-------------------|
|
|
1170
|
+
| Approach | Optimal EIF-based weighting | Separate 2x2 DiD aggregation |
|
|
1171
|
+
| PT assumption | PT-All (stronger) or PT-Post | Conditional PT |
|
|
1172
|
+
| Efficiency | Achieves semiparametric bound | Not efficient |
|
|
1173
|
+
| Covariates | Not yet (Phase 2) | Supported (OR, IPW, DR) |
|
|
1174
|
+
| When to choose | Maximum efficiency, PT-All credible | Covariates needed, weaker PT |
|
|
1175
|
+
|
|
1074
1176
|
### Triple Difference (DDD)
|
|
1075
1177
|
|
|
1076
1178
|
Triple Difference (DDD) is used when treatment requires satisfying two criteria: belonging to a treated **group** AND being in an eligible **partition**. The `TripleDifference` class implements the methodology from Ortiz-Villavicencio & Sant'Anna (2025), which correctly handles covariate adjustment (unlike naive implementations).
|
|
@@ -1466,7 +1568,7 @@ trop = TROP(
|
|
|
1466
1568
|
|
|
1467
1569
|
```python
|
|
1468
1570
|
TROP(
|
|
1469
|
-
method='
|
|
1571
|
+
method='local', # Estimation method: 'local' (default) or 'global'
|
|
1470
1572
|
lambda_time_grid=None, # Time decay grid (default: [0, 0.1, 0.5, 1, 2, 5])
|
|
1471
1573
|
lambda_unit_grid=None, # Unit distance grid (default: [0, 0.1, 0.5, 1, 2, 5])
|
|
1472
1574
|
lambda_nn_grid=None, # Nuclear norm grid (default: [0, 0.01, 0.1, 1, 10])
|
|
@@ -1479,8 +1581,8 @@ TROP(
|
|
|
1479
1581
|
```
|
|
1480
1582
|
|
|
1481
1583
|
**Estimation methods:**
|
|
1482
|
-
- `'
|
|
1483
|
-
- `'
|
|
1584
|
+
- `'local'` (default): Per-observation model fitting following Algorithm 2 of the paper. Computes observation-specific weights and fits a model for each treated observation, then averages the individual treatment effects. More flexible but computationally intensive.
|
|
1585
|
+
- `'global'`: Global weighted least squares optimization. Fits a single model on control observations with global weights, then computes per-observation treatment effects as residuals. Faster but uses global rather than observation-specific weights.
|
|
1484
1586
|
|
|
1485
1587
|
**Convenience function:**
|
|
1486
1588
|
|
|
@@ -2858,6 +2960,22 @@ The `HonestDiD` module implements sensitivity analysis methods for relaxing the
|
|
|
2858
2960
|
|
|
2859
2961
|
- **Cunningham, S. (2021).** *Causal Inference: The Mixtape*. Yale University Press. [https://mixtape.scunning.com/](https://mixtape.scunning.com/)
|
|
2860
2962
|
|
|
2963
|
+
## Citing diff-diff
|
|
2964
|
+
|
|
2965
|
+
If you use diff-diff in your research, please cite it:
|
|
2966
|
+
|
|
2967
|
+
```bibtex
|
|
2968
|
+
@software{diff_diff,
|
|
2969
|
+
title = {diff-diff: Difference-in-Differences Causal Inference for Python},
|
|
2970
|
+
author = {{diff-diff contributors}},
|
|
2971
|
+
url = {https://github.com/igerber/diff-diff},
|
|
2972
|
+
license = {MIT},
|
|
2973
|
+
}
|
|
2974
|
+
```
|
|
2975
|
+
|
|
2976
|
+
See [`CITATION.cff`](CITATION.cff) for the full citation metadata.
|
|
2977
|
+
|
|
2861
2978
|
## License
|
|
2862
2979
|
|
|
2863
2980
|
MIT License
|
|
2981
|
+
|
|
@@ -1,43 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: diff-diff
|
|
3
|
-
Version: 2.7.0
|
|
4
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
5
|
-
Classifier: Intended Audience :: Science/Research
|
|
6
|
-
Classifier: Operating System :: OS Independent
|
|
7
|
-
Classifier: Programming Language :: Python :: 3
|
|
8
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
-
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
14
|
-
Requires-Dist: numpy>=1.20.0
|
|
15
|
-
Requires-Dist: pandas>=1.3.0
|
|
16
|
-
Requires-Dist: scipy>=1.7.0
|
|
17
|
-
Requires-Dist: pytest>=7.0 ; extra == 'dev'
|
|
18
|
-
Requires-Dist: pytest-xdist>=3.0 ; extra == 'dev'
|
|
19
|
-
Requires-Dist: pytest-cov>=4.0 ; extra == 'dev'
|
|
20
|
-
Requires-Dist: black>=23.0 ; extra == 'dev'
|
|
21
|
-
Requires-Dist: ruff>=0.1.0 ; extra == 'dev'
|
|
22
|
-
Requires-Dist: mypy>=1.0 ; extra == 'dev'
|
|
23
|
-
Requires-Dist: maturin>=1.4,<2.0 ; extra == 'dev'
|
|
24
|
-
Requires-Dist: sphinx>=6.0 ; extra == 'docs'
|
|
25
|
-
Requires-Dist: sphinx-rtd-theme>=1.0 ; extra == 'docs'
|
|
26
|
-
Provides-Extra: dev
|
|
27
|
-
Provides-Extra: docs
|
|
28
|
-
Summary: A library for Difference-in-Differences causal inference analysis
|
|
29
|
-
Keywords: causal-inference,difference-in-differences,econometrics,statistics,treatment-effects
|
|
30
|
-
Author: diff-diff contributors
|
|
31
|
-
License-Expression: MIT
|
|
32
|
-
Requires-Python: >=3.9, <3.14
|
|
33
|
-
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
34
|
-
Project-URL: Documentation, https://diff-diff.readthedocs.io
|
|
35
|
-
Project-URL: Homepage, https://github.com/igerber/diff-diff
|
|
36
|
-
Project-URL: Issues, https://github.com/igerber/diff-diff/issues
|
|
37
|
-
Project-URL: Repository, https://github.com/igerber/diff-diff
|
|
38
|
-
|
|
39
1
|
# diff-diff
|
|
40
2
|
|
|
3
|
+
[](https://pypi.org/project/diff-diff/)
|
|
4
|
+
[](https://pypi.org/project/diff-diff/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://pypi.org/project/diff-diff/)
|
|
7
|
+
[](https://diff-diff.readthedocs.io/en/stable/)
|
|
8
|
+
|
|
41
9
|
A Python library for Difference-in-Differences (DiD) causal inference analysis with an sklearn-like API and statsmodels-style outputs.
|
|
42
10
|
|
|
43
11
|
## Installation
|
|
@@ -108,7 +76,7 @@ Signif. codes: '***' 0.001, '**' 0.01, '*' 0.05, '.' 0.1
|
|
|
108
76
|
- **Wild cluster bootstrap**: Valid inference with few clusters (<50) using Rademacher, Webb, or Mammen weights
|
|
109
77
|
- **Panel data support**: Two-way fixed effects estimator for panel designs
|
|
110
78
|
- **Multi-period analysis**: Event-study style DiD with period-specific treatment effects
|
|
111
|
-
- **Staggered adoption**: Callaway-Sant'Anna (2021), Sun-Abraham (2021), Borusyak-Jaravel-Spiess (2024) imputation, Two-Stage DiD (Gardner 2022),
|
|
79
|
+
- **Staggered adoption**: Callaway-Sant'Anna (2021), Sun-Abraham (2021), Borusyak-Jaravel-Spiess (2024) imputation, Two-Stage DiD (Gardner 2022), Stacked DiD (Wing, Freedman & Hollingsworth 2024), and Efficient DiD (Chen, Sant'Anna & Xie 2025) estimators for heterogeneous treatment timing
|
|
112
80
|
- **Triple Difference (DDD)**: Ortiz-Villavicencio & Sant'Anna (2025) estimators with proper covariate handling
|
|
113
81
|
- **Synthetic DiD**: Combined DiD with synthetic control for improved robustness
|
|
114
82
|
- **Triply Robust Panel (TROP)**: Factor-adjusted DiD with synthetic weights (Athey et al. 2025)
|
|
@@ -163,6 +131,7 @@ We provide Jupyter notebook tutorials in `docs/tutorials/`:
|
|
|
163
131
|
| `11_imputation_did.ipynb` | Imputation DiD (Borusyak et al. 2024), pre-trend test, efficiency comparison |
|
|
164
132
|
| `12_two_stage_did.ipynb` | Two-Stage DiD (Gardner 2022), GMM sandwich variance, per-observation effects |
|
|
165
133
|
| `13_stacked_did.ipynb` | Stacked DiD (Wing et al. 2024), Q-weights, sub-experiment inspection, trimming, clean control definitions |
|
|
134
|
+
| `15_efficient_did.ipynb` | Efficient DiD (Chen et al. 2025), optimal weighting, PT-All vs PT-Post, efficiency gains, bootstrap inference |
|
|
166
135
|
|
|
167
136
|
## Data Preparation
|
|
168
137
|
|
|
@@ -1109,6 +1078,56 @@ results = stacked_did(
|
|
|
1109
1078
|
)
|
|
1110
1079
|
```
|
|
1111
1080
|
|
|
1081
|
+
### Efficient DiD (Chen, Sant'Anna & Xie 2025)
|
|
1082
|
+
|
|
1083
|
+
Efficient DiD achieves the semiparametric efficiency bound for ATT estimation in staggered adoption designs. It optimally weights across all valid comparison groups and baselines via the inverse covariance matrix Omega*, producing tighter confidence intervals than standard estimators like Callaway-Sant'Anna when the stronger PT-All assumption holds.
|
|
1084
|
+
|
|
1085
|
+
```python
|
|
1086
|
+
from diff_diff import EfficientDiD, generate_staggered_data
|
|
1087
|
+
|
|
1088
|
+
# Generate sample data
|
|
1089
|
+
data = generate_staggered_data(n_units=300, n_periods=10,
|
|
1090
|
+
cohort_periods=[4, 6, 8], seed=42)
|
|
1091
|
+
|
|
1092
|
+
# Fit with PT-All (overidentified, tighter SEs)
|
|
1093
|
+
edid = EfficientDiD(pt_assumption="all")
|
|
1094
|
+
results = edid.fit(data, outcome='outcome', unit='unit',
|
|
1095
|
+
time='period', first_treat='first_treat',
|
|
1096
|
+
aggregate='all')
|
|
1097
|
+
results.print_summary()
|
|
1098
|
+
|
|
1099
|
+
# PT-Post mode (matches CS for post-treatment effects)
|
|
1100
|
+
edid_post = EfficientDiD(pt_assumption="post")
|
|
1101
|
+
results_post = edid_post.fit(data, outcome='outcome', unit='unit',
|
|
1102
|
+
time='period', first_treat='first_treat')
|
|
1103
|
+
```
|
|
1104
|
+
|
|
1105
|
+
**Parameters:**
|
|
1106
|
+
|
|
1107
|
+
```python
|
|
1108
|
+
EfficientDiD(
|
|
1109
|
+
pt_assumption='all', # 'all' (overidentified) or 'post' (matches CS post-treatment ATT)
|
|
1110
|
+
alpha=0.05, # Significance level
|
|
1111
|
+
n_bootstrap=0, # Bootstrap iterations (0 = analytical only)
|
|
1112
|
+
bootstrap_weights='rademacher', # 'rademacher', 'mammen', or 'webb'
|
|
1113
|
+
seed=None, # Random seed
|
|
1114
|
+
anticipation=0, # Anticipation periods
|
|
1115
|
+
)
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
> **Note:** Phase 1 supports the no-covariates path only. Use CallawaySantAnna with
|
|
1119
|
+
> `estimation_method='dr'` if you need covariate adjustment.
|
|
1120
|
+
|
|
1121
|
+
**When to use Efficient DiD vs Callaway-Sant'Anna:**
|
|
1122
|
+
|
|
1123
|
+
| Aspect | Efficient DiD | Callaway-Sant'Anna |
|
|
1124
|
+
|--------|--------------|-------------------|
|
|
1125
|
+
| Approach | Optimal EIF-based weighting | Separate 2x2 DiD aggregation |
|
|
1126
|
+
| PT assumption | PT-All (stronger) or PT-Post | Conditional PT |
|
|
1127
|
+
| Efficiency | Achieves semiparametric bound | Not efficient |
|
|
1128
|
+
| Covariates | Not yet (Phase 2) | Supported (OR, IPW, DR) |
|
|
1129
|
+
| When to choose | Maximum efficiency, PT-All credible | Covariates needed, weaker PT |
|
|
1130
|
+
|
|
1112
1131
|
### Triple Difference (DDD)
|
|
1113
1132
|
|
|
1114
1133
|
Triple Difference (DDD) is used when treatment requires satisfying two criteria: belonging to a treated **group** AND being in an eligible **partition**. The `TripleDifference` class implements the methodology from Ortiz-Villavicencio & Sant'Anna (2025), which correctly handles covariate adjustment (unlike naive implementations).
|
|
@@ -1504,7 +1523,7 @@ trop = TROP(
|
|
|
1504
1523
|
|
|
1505
1524
|
```python
|
|
1506
1525
|
TROP(
|
|
1507
|
-
method='
|
|
1526
|
+
method='local', # Estimation method: 'local' (default) or 'global'
|
|
1508
1527
|
lambda_time_grid=None, # Time decay grid (default: [0, 0.1, 0.5, 1, 2, 5])
|
|
1509
1528
|
lambda_unit_grid=None, # Unit distance grid (default: [0, 0.1, 0.5, 1, 2, 5])
|
|
1510
1529
|
lambda_nn_grid=None, # Nuclear norm grid (default: [0, 0.01, 0.1, 1, 10])
|
|
@@ -1517,8 +1536,8 @@ TROP(
|
|
|
1517
1536
|
```
|
|
1518
1537
|
|
|
1519
1538
|
**Estimation methods:**
|
|
1520
|
-
- `'
|
|
1521
|
-
- `'
|
|
1539
|
+
- `'local'` (default): Per-observation model fitting following Algorithm 2 of the paper. Computes observation-specific weights and fits a model for each treated observation, then averages the individual treatment effects. More flexible but computationally intensive.
|
|
1540
|
+
- `'global'`: Global weighted least squares optimization. Fits a single model on control observations with global weights, then computes per-observation treatment effects as residuals. Faster but uses global rather than observation-specific weights.
|
|
1522
1541
|
|
|
1523
1542
|
**Convenience function:**
|
|
1524
1543
|
|
|
@@ -2896,7 +2915,21 @@ The `HonestDiD` module implements sensitivity analysis methods for relaxing the
|
|
|
2896
2915
|
|
|
2897
2916
|
- **Cunningham, S. (2021).** *Causal Inference: The Mixtape*. Yale University Press. [https://mixtape.scunning.com/](https://mixtape.scunning.com/)
|
|
2898
2917
|
|
|
2918
|
+
## Citing diff-diff
|
|
2919
|
+
|
|
2920
|
+
If you use diff-diff in your research, please cite it:
|
|
2921
|
+
|
|
2922
|
+
```bibtex
|
|
2923
|
+
@software{diff_diff,
|
|
2924
|
+
title = {diff-diff: Difference-in-Differences Causal Inference for Python},
|
|
2925
|
+
author = {{diff-diff contributors}},
|
|
2926
|
+
url = {https://github.com/igerber/diff-diff},
|
|
2927
|
+
license = {MIT},
|
|
2928
|
+
}
|
|
2929
|
+
```
|
|
2930
|
+
|
|
2931
|
+
See [`CITATION.cff`](CITATION.cff) for the full citation metadata.
|
|
2932
|
+
|
|
2899
2933
|
## License
|
|
2900
2934
|
|
|
2901
2935
|
MIT License
|
|
2902
|
-
|
|
@@ -23,13 +23,13 @@ try:
|
|
|
23
23
|
project_simplex as _rust_project_simplex,
|
|
24
24
|
solve_ols as _rust_solve_ols,
|
|
25
25
|
compute_robust_vcov as _rust_compute_robust_vcov,
|
|
26
|
-
# TROP estimator acceleration (
|
|
26
|
+
# TROP estimator acceleration (local method)
|
|
27
27
|
compute_unit_distance_matrix as _rust_unit_distance_matrix,
|
|
28
28
|
loocv_grid_search as _rust_loocv_grid_search,
|
|
29
29
|
bootstrap_trop_variance as _rust_bootstrap_trop_variance,
|
|
30
|
-
# TROP estimator acceleration (
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
# TROP estimator acceleration (global method)
|
|
31
|
+
loocv_grid_search_global as _rust_loocv_grid_search_global,
|
|
32
|
+
bootstrap_trop_variance_global as _rust_bootstrap_trop_variance_global,
|
|
33
33
|
# SDID weights (Frank-Wolfe matching R's synthdid)
|
|
34
34
|
compute_sdid_unit_weights as _rust_sdid_unit_weights,
|
|
35
35
|
compute_time_weights as _rust_compute_time_weights,
|
|
@@ -46,13 +46,13 @@ except ImportError:
|
|
|
46
46
|
_rust_project_simplex = None
|
|
47
47
|
_rust_solve_ols = None
|
|
48
48
|
_rust_compute_robust_vcov = None
|
|
49
|
-
# TROP estimator acceleration (
|
|
49
|
+
# TROP estimator acceleration (local method)
|
|
50
50
|
_rust_unit_distance_matrix = None
|
|
51
51
|
_rust_loocv_grid_search = None
|
|
52
52
|
_rust_bootstrap_trop_variance = None
|
|
53
|
-
# TROP estimator acceleration (
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
# TROP estimator acceleration (global method)
|
|
54
|
+
_rust_loocv_grid_search_global = None
|
|
55
|
+
_rust_bootstrap_trop_variance_global = None
|
|
56
56
|
# SDID weights (Frank-Wolfe matching R's synthdid)
|
|
57
57
|
_rust_sdid_unit_weights = None
|
|
58
58
|
_rust_compute_time_weights = None
|
|
@@ -69,13 +69,13 @@ if _backend_env == 'python':
|
|
|
69
69
|
_rust_project_simplex = None
|
|
70
70
|
_rust_solve_ols = None
|
|
71
71
|
_rust_compute_robust_vcov = None
|
|
72
|
-
# TROP estimator acceleration (
|
|
72
|
+
# TROP estimator acceleration (local method)
|
|
73
73
|
_rust_unit_distance_matrix = None
|
|
74
74
|
_rust_loocv_grid_search = None
|
|
75
75
|
_rust_bootstrap_trop_variance = None
|
|
76
|
-
# TROP estimator acceleration (
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
# TROP estimator acceleration (global method)
|
|
77
|
+
_rust_loocv_grid_search_global = None
|
|
78
|
+
_rust_bootstrap_trop_variance_global = None
|
|
79
79
|
# SDID weights (Frank-Wolfe matching R's synthdid)
|
|
80
80
|
_rust_sdid_unit_weights = None
|
|
81
81
|
_rust_compute_time_weights = None
|
|
@@ -118,13 +118,13 @@ __all__ = [
|
|
|
118
118
|
'_rust_project_simplex',
|
|
119
119
|
'_rust_solve_ols',
|
|
120
120
|
'_rust_compute_robust_vcov',
|
|
121
|
-
# TROP estimator acceleration (
|
|
121
|
+
# TROP estimator acceleration (local method)
|
|
122
122
|
'_rust_unit_distance_matrix',
|
|
123
123
|
'_rust_loocv_grid_search',
|
|
124
124
|
'_rust_bootstrap_trop_variance',
|
|
125
|
-
# TROP estimator acceleration (
|
|
126
|
-
'
|
|
127
|
-
'
|
|
125
|
+
# TROP estimator acceleration (global method)
|
|
126
|
+
'_rust_loocv_grid_search_global',
|
|
127
|
+
'_rust_bootstrap_trop_variance_global',
|
|
128
128
|
# SDID weights (Frank-Wolfe matching R's synthdid)
|
|
129
129
|
'_rust_sdid_unit_weights',
|
|
130
130
|
'_rust_compute_time_weights',
|
|
@@ -266,7 +266,7 @@ def load_castle_doctrine(force_download: bool = False) -> pd.DataFrame:
|
|
|
266
266
|
... outcome="homicide_rate",
|
|
267
267
|
... unit="state",
|
|
268
268
|
... time="year",
|
|
269
|
-
...
|
|
269
|
+
... first_treat="first_treat"
|
|
270
270
|
... )
|
|
271
271
|
"""
|
|
272
272
|
url = "https://raw.githubusercontent.com/causaldata/causal_datasets/main/castle/castle.csv"
|
|
@@ -412,7 +412,7 @@ def load_divorce_laws(force_download: bool = False) -> pd.DataFrame:
|
|
|
412
412
|
... outcome="divorce_rate",
|
|
413
413
|
... unit="state",
|
|
414
414
|
... time="year",
|
|
415
|
-
...
|
|
415
|
+
... first_treat="first_treat"
|
|
416
416
|
... )
|
|
417
417
|
"""
|
|
418
418
|
# Try to load from causaldata repository
|
|
@@ -571,7 +571,7 @@ def load_mpdta(force_download: bool = False) -> pd.DataFrame:
|
|
|
571
571
|
... outcome="lemp",
|
|
572
572
|
... unit="countyreal",
|
|
573
573
|
... time="year",
|
|
574
|
-
...
|
|
574
|
+
... first_treat="first_treat"
|
|
575
575
|
... )
|
|
576
576
|
"""
|
|
577
577
|
# mpdta is available from the did package documentation
|