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.
Files changed (52) hide show
  1. diff_diff-2.7.0/README.md → diff_diff-2.7.2/PKG-INFO +122 -4
  2. diff_diff-2.7.0/PKG-INFO → diff_diff-2.7.2/README.md +76 -43
  3. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/__init__.py +1 -1
  4. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/_backend.py +16 -16
  5. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/datasets.py +3 -3
  6. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/linalg.py +217 -27
  7. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/staggered.py +306 -266
  8. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/staggered_results.py +89 -68
  9. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/sun_abraham.py +9 -15
  10. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/triple_diff.py +241 -258
  11. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/trop.py +70 -53
  12. {diff_diff-2.7.0 → diff_diff-2.7.2}/pyproject.toml +22 -5
  13. {diff_diff-2.7.0 → diff_diff-2.7.2}/rust/Cargo.lock +1 -1
  14. {diff_diff-2.7.0 → diff_diff-2.7.2}/rust/Cargo.toml +1 -1
  15. {diff_diff-2.7.0 → diff_diff-2.7.2}/rust/src/lib.rs +4 -4
  16. {diff_diff-2.7.0 → diff_diff-2.7.2}/rust/src/trop.rs +16 -12
  17. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/bacon.py +0 -0
  18. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/bootstrap_utils.py +0 -0
  19. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/continuous_did.py +0 -0
  20. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/continuous_did_bspline.py +0 -0
  21. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/continuous_did_results.py +0 -0
  22. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/diagnostics.py +0 -0
  23. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/efficient_did.py +0 -0
  24. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/efficient_did_bootstrap.py +0 -0
  25. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/efficient_did_results.py +0 -0
  26. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/efficient_did_weights.py +0 -0
  27. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/estimators.py +0 -0
  28. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/honest_did.py +0 -0
  29. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/imputation.py +0 -0
  30. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/imputation_bootstrap.py +0 -0
  31. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/imputation_results.py +0 -0
  32. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/power.py +0 -0
  33. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/prep.py +0 -0
  34. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/prep_dgp.py +0 -0
  35. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/pretrends.py +0 -0
  36. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/results.py +0 -0
  37. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/stacked_did.py +0 -0
  38. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/stacked_did_results.py +0 -0
  39. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/staggered_aggregation.py +0 -0
  40. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/staggered_bootstrap.py +0 -0
  41. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/synthetic_did.py +0 -0
  42. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/trop_results.py +0 -0
  43. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/twfe.py +0 -0
  44. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/two_stage.py +0 -0
  45. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/two_stage_bootstrap.py +0 -0
  46. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/two_stage_results.py +0 -0
  47. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/utils.py +0 -0
  48. {diff_diff-2.7.0 → diff_diff-2.7.2}/diff_diff/visualization.py +0 -0
  49. {diff_diff-2.7.0 → diff_diff-2.7.2}/rust/build.rs +0 -0
  50. {diff_diff-2.7.0 → diff_diff-2.7.2}/rust/src/bootstrap.rs +0 -0
  51. {diff_diff-2.7.0 → diff_diff-2.7.2}/rust/src/linalg.rs +0 -0
  52. {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
+ [![PyPI version](https://img.shields.io/pypi/v/diff-diff.svg)](https://pypi.org/project/diff-diff/)
49
+ [![Python versions](https://img.shields.io/pypi/pyversions/diff-diff.svg)](https://pypi.org/project/diff-diff/)
50
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
51
+ [![Downloads](https://img.shields.io/pypi/dm/diff-diff.svg)](https://pypi.org/project/diff-diff/)
52
+ [![Documentation](https://readthedocs.org/projects/diff-diff/badge/?version=stable)](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), and Stacked DiD (Wing, Freedman & Hollingsworth 2024) estimators for heterogeneous treatment timing
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='twostep', # Estimation method: 'twostep' (default) or 'joint'
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
- - `'twostep'` (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.
1483
- - `'joint'`: Joint weighted least squares optimization. Estimates a single scalar treatment effect τ along with fixed effects and optional low-rank factor adjustment. Faster but assumes homogeneous treatment effects.
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
+ [![PyPI version](https://img.shields.io/pypi/v/diff-diff.svg)](https://pypi.org/project/diff-diff/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/diff-diff.svg)](https://pypi.org/project/diff-diff/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+ [![Downloads](https://img.shields.io/pypi/dm/diff-diff.svg)](https://pypi.org/project/diff-diff/)
7
+ [![Documentation](https://readthedocs.org/projects/diff-diff/badge/?version=stable)](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), and Stacked DiD (Wing, Freedman & Hollingsworth 2024) estimators for heterogeneous treatment timing
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='twostep', # Estimation method: 'twostep' (default) or 'joint'
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
- - `'twostep'` (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.
1521
- - `'joint'`: Joint weighted least squares optimization. Estimates a single scalar treatment effect τ along with fixed effects and optional low-rank factor adjustment. Faster but assumes homogeneous treatment effects.
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
-
@@ -179,7 +179,7 @@ Stacked = StackedDiD
179
179
  Bacon = BaconDecomposition
180
180
  EDiD = EfficientDiD
181
181
 
182
- __version__ = "2.7.0"
182
+ __version__ = "2.7.2"
183
183
  __all__ = [
184
184
  # Estimators
185
185
  "DifferenceInDifferences",
@@ -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 (twostep method)
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 (joint method)
31
- loocv_grid_search_joint as _rust_loocv_grid_search_joint,
32
- bootstrap_trop_variance_joint as _rust_bootstrap_trop_variance_joint,
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 (twostep method)
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 (joint method)
54
- _rust_loocv_grid_search_joint = None
55
- _rust_bootstrap_trop_variance_joint = None
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 (twostep method)
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 (joint method)
77
- _rust_loocv_grid_search_joint = None
78
- _rust_bootstrap_trop_variance_joint = None
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 (twostep method)
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 (joint method)
126
- '_rust_loocv_grid_search_joint',
127
- '_rust_bootstrap_trop_variance_joint',
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
- ... cohort="first_treat"
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
- ... cohort="first_treat"
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
- ... cohort="first_treat"
574
+ ... first_treat="first_treat"
575
575
  ... )
576
576
  """
577
577
  # mpdta is available from the did package documentation