diff-diff 0.4.0__tar.gz → 0.6.0__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-0.4.0 → diff_diff-0.6.0}/PKG-INFO +361 -15
- {diff_diff-0.4.0 → diff_diff-0.6.0}/README.md +359 -13
- {diff_diff-0.4.0 → diff_diff-0.6.0}/diff_diff/__init__.py +66 -21
- diff_diff-0.6.0/diff_diff/diagnostics.py +888 -0
- {diff_diff-0.4.0 → diff_diff-0.6.0}/diff_diff/estimators.py +152 -38
- diff_diff-0.6.0/diff_diff/honest_did.py +1491 -0
- {diff_diff-0.4.0 → diff_diff-0.6.0}/diff_diff/prep.py +1 -1
- {diff_diff-0.4.0 → diff_diff-0.6.0}/diff_diff/results.py +26 -7
- {diff_diff-0.4.0 → diff_diff-0.6.0}/diff_diff/staggered.py +341 -52
- {diff_diff-0.4.0 → diff_diff-0.6.0}/diff_diff/utils.py +393 -17
- {diff_diff-0.4.0 → diff_diff-0.6.0}/diff_diff/visualization.py +350 -1
- {diff_diff-0.4.0 → diff_diff-0.6.0}/diff_diff.egg-info/PKG-INFO +361 -15
- {diff_diff-0.4.0 → diff_diff-0.6.0}/diff_diff.egg-info/SOURCES.txt +7 -1
- {diff_diff-0.4.0 → diff_diff-0.6.0}/pyproject.toml +11 -3
- diff_diff-0.6.0/tests/test_diagnostics.py +674 -0
- {diff_diff-0.4.0 → diff_diff-0.6.0}/tests/test_estimators.py +5 -3
- diff_diff-0.6.0/tests/test_honest_did.py +699 -0
- diff_diff-0.6.0/tests/test_staggered.py +752 -0
- diff_diff-0.6.0/tests/test_utils.py +1270 -0
- diff_diff-0.6.0/tests/test_wild_bootstrap.py +623 -0
- diff_diff-0.4.0/tests/test_staggered.py +0 -390
- {diff_diff-0.4.0 → diff_diff-0.6.0}/diff_diff.egg-info/dependency_links.txt +0 -0
- {diff_diff-0.4.0 → diff_diff-0.6.0}/diff_diff.egg-info/requires.txt +0 -0
- {diff_diff-0.4.0 → diff_diff-0.6.0}/diff_diff.egg-info/top_level.txt +0 -0
- {diff_diff-0.4.0 → diff_diff-0.6.0}/setup.cfg +0 -0
- {diff_diff-0.4.0 → diff_diff-0.6.0}/tests/test_prep.py +7 -7
- {diff_diff-0.4.0 → diff_diff-0.6.0}/tests/test_visualization.py +1 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: diff-diff
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: A library for Difference-in-Differences causal inference analysis
|
|
5
5
|
Author: diff-diff contributors
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/igerber/diff-diff
|
|
8
|
-
Project-URL: Documentation, https://
|
|
8
|
+
Project-URL: Documentation, https://diff-diff.readthedocs.io
|
|
9
9
|
Project-URL: Repository, https://github.com/igerber/diff-diff
|
|
10
10
|
Project-URL: Issues, https://github.com/igerber/diff-diff/issues
|
|
11
11
|
Keywords: causal-inference,difference-in-differences,econometrics,statistics,treatment-effects
|
|
@@ -69,28 +69,28 @@ did = DifferenceInDifferences()
|
|
|
69
69
|
results = did.fit(data, outcome='outcome', treatment='treated', time='post')
|
|
70
70
|
|
|
71
71
|
# View results
|
|
72
|
-
print(results) # DiDResults(ATT=3.
|
|
72
|
+
print(results) # DiDResults(ATT=3.0000, SE=1.7321, p=0.1583)
|
|
73
73
|
results.print_summary()
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
Output:
|
|
77
77
|
```
|
|
78
78
|
======================================================================
|
|
79
|
-
|
|
79
|
+
Difference-in-Differences Estimation Results
|
|
80
80
|
======================================================================
|
|
81
81
|
|
|
82
|
-
Observations:
|
|
83
|
-
Treated units:
|
|
84
|
-
Control units:
|
|
85
|
-
R-squared:
|
|
82
|
+
Observations: 8
|
|
83
|
+
Treated units: 4
|
|
84
|
+
Control units: 4
|
|
85
|
+
R-squared: 0.9055
|
|
86
86
|
|
|
87
87
|
----------------------------------------------------------------------
|
|
88
|
-
Parameter
|
|
88
|
+
Parameter Estimate Std. Err. t-stat P>|t|
|
|
89
89
|
----------------------------------------------------------------------
|
|
90
|
-
ATT
|
|
90
|
+
ATT 3.0000 1.7321 1.732 0.1583
|
|
91
91
|
----------------------------------------------------------------------
|
|
92
92
|
|
|
93
|
-
95% Confidence Interval: [
|
|
93
|
+
95% Confidence Interval: [-1.8089, 7.8089]
|
|
94
94
|
|
|
95
95
|
Signif. codes: '***' 0.001, '**' 0.01, '*' 0.05, '.' 0.1
|
|
96
96
|
======================================================================
|
|
@@ -102,14 +102,29 @@ Signif. codes: '***' 0.001, '**' 0.01, '*' 0.05, '.' 0.1
|
|
|
102
102
|
- **Pythonic results**: Easy access to coefficients, standard errors, and confidence intervals
|
|
103
103
|
- **Multiple interfaces**: Column names or R-style formulas
|
|
104
104
|
- **Robust inference**: Heteroskedasticity-robust (HC1) and cluster-robust standard errors
|
|
105
|
+
- **Wild cluster bootstrap**: Valid inference with few clusters (<50) using Rademacher, Webb, or Mammen weights
|
|
105
106
|
- **Panel data support**: Two-way fixed effects estimator for panel designs
|
|
106
107
|
- **Multi-period analysis**: Event-study style DiD with period-specific treatment effects
|
|
107
108
|
- **Staggered adoption**: Callaway-Sant'Anna (2021) estimator for heterogeneous treatment timing
|
|
108
109
|
- **Synthetic DiD**: Combined DiD with synthetic control for improved robustness
|
|
109
110
|
- **Event study plots**: Publication-ready visualization of treatment effects
|
|
110
111
|
- **Parallel trends testing**: Multiple methods including equivalence tests
|
|
112
|
+
- **Placebo tests**: Comprehensive diagnostics including fake timing, fake group, permutation, and leave-one-out tests
|
|
113
|
+
- **Honest DiD sensitivity analysis**: Rambachan-Roth (2023) bounds and breakdown analysis for parallel trends violations
|
|
111
114
|
- **Data prep utilities**: Helper functions for common data preparation tasks
|
|
112
115
|
|
|
116
|
+
## Tutorials
|
|
117
|
+
|
|
118
|
+
We provide Jupyter notebook tutorials in `docs/tutorials/`:
|
|
119
|
+
|
|
120
|
+
| Notebook | Description |
|
|
121
|
+
|----------|-------------|
|
|
122
|
+
| `01_basic_did.ipynb` | Basic 2x2 DiD, formula interface, covariates, fixed effects, cluster-robust SE, wild bootstrap |
|
|
123
|
+
| `02_staggered_did.ipynb` | Staggered adoption with Callaway-Sant'Anna, group-time effects, aggregation methods |
|
|
124
|
+
| `03_synthetic_did.ipynb` | Synthetic DiD, unit/time weights, inference methods, regularization |
|
|
125
|
+
| `04_parallel_trends.ipynb` | Testing parallel trends, equivalence tests, placebo tests, diagnostics |
|
|
126
|
+
| `05_honest_did.ipynb` | Honest DiD sensitivity analysis, bounds, breakdown values, visualization |
|
|
127
|
+
|
|
113
128
|
## Data Preparation
|
|
114
129
|
|
|
115
130
|
diff-diff provides utility functions to help prepare your data for DiD analysis. These functions handle common data transformation tasks like creating treatment indicators, reshaping panel data, and validating data formats.
|
|
@@ -523,6 +538,36 @@ results = did.fit(
|
|
|
523
538
|
)
|
|
524
539
|
```
|
|
525
540
|
|
|
541
|
+
### Wild Cluster Bootstrap
|
|
542
|
+
|
|
543
|
+
When you have few clusters (<50), standard cluster-robust SEs are biased. Wild cluster bootstrap provides valid inference even with 5-10 clusters.
|
|
544
|
+
|
|
545
|
+
```python
|
|
546
|
+
# Use wild bootstrap for inference
|
|
547
|
+
did = DifferenceInDifferences(
|
|
548
|
+
cluster='state',
|
|
549
|
+
inference='wild_bootstrap',
|
|
550
|
+
n_bootstrap=999,
|
|
551
|
+
bootstrap_weights='rademacher', # or 'webb' for <10 clusters, 'mammen'
|
|
552
|
+
seed=42
|
|
553
|
+
)
|
|
554
|
+
results = did.fit(data, outcome='y', treatment='treated', time='post')
|
|
555
|
+
|
|
556
|
+
# Results include bootstrap-based SE and p-value
|
|
557
|
+
print(f"ATT: {results.att:.3f} (SE: {results.se:.3f})")
|
|
558
|
+
print(f"P-value: {results.p_value:.4f}")
|
|
559
|
+
print(f"95% CI: {results.conf_int}")
|
|
560
|
+
print(f"Inference method: {results.inference_method}")
|
|
561
|
+
print(f"Number of clusters: {results.n_clusters}")
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
**Weight types:**
|
|
565
|
+
- `'rademacher'` - Default, ±1 with p=0.5, good for most cases
|
|
566
|
+
- `'webb'` - 6-point distribution, recommended for <10 clusters
|
|
567
|
+
- `'mammen'` - Two-point distribution, alternative to Rademacher
|
|
568
|
+
|
|
569
|
+
Works with `DifferenceInDifferences` and `TwoWayFixedEffects` estimators.
|
|
570
|
+
|
|
526
571
|
### Two-Way Fixed Effects (Panel Data)
|
|
527
572
|
|
|
528
573
|
```python
|
|
@@ -688,14 +733,31 @@ CallawaySantAnna(
|
|
|
688
733
|
estimation_method='dr', # 'dr', 'ipw', or 'reg'
|
|
689
734
|
alpha=0.05, # Significance level
|
|
690
735
|
cluster=None, # Column for cluster SEs
|
|
691
|
-
n_bootstrap=0, #
|
|
736
|
+
n_bootstrap=0, # Bootstrap iterations (0 = analytical SEs)
|
|
692
737
|
seed=None # Random seed
|
|
693
738
|
)
|
|
694
739
|
```
|
|
695
740
|
|
|
741
|
+
**Covariate adjustment for conditional parallel trends:**
|
|
742
|
+
|
|
743
|
+
When parallel trends only holds conditional on covariates, use the `covariates` parameter:
|
|
744
|
+
|
|
745
|
+
```python
|
|
746
|
+
# Doubly robust estimation with covariates
|
|
747
|
+
cs = CallawaySantAnna(estimation_method='dr') # 'dr', 'ipw', or 'reg'
|
|
748
|
+
results = cs.fit(
|
|
749
|
+
data,
|
|
750
|
+
outcome='sales',
|
|
751
|
+
unit='firm_id',
|
|
752
|
+
time='year',
|
|
753
|
+
first_treat='first_treat',
|
|
754
|
+
covariates=['size', 'age', 'industry'], # Covariates for conditional PT
|
|
755
|
+
aggregate='event_study'
|
|
756
|
+
)
|
|
757
|
+
```
|
|
758
|
+
|
|
696
759
|
**Current limitations:**
|
|
697
|
-
- Bootstrap inference (`n_bootstrap > 0`) is not yet implemented
|
|
698
|
-
- Covariate adjustment for conditional parallel trends is not yet implemented
|
|
760
|
+
- Bootstrap inference (`n_bootstrap > 0`) is not yet fully implemented
|
|
699
761
|
|
|
700
762
|
### Event Study Visualization
|
|
701
763
|
|
|
@@ -972,6 +1034,195 @@ print(f"TOST p-value: {results['tost_p_value']:.4f}")
|
|
|
972
1034
|
print(f"Trends equivalent: {results['equivalent']}")
|
|
973
1035
|
```
|
|
974
1036
|
|
|
1037
|
+
### Honest DiD Sensitivity Analysis (Rambachan-Roth)
|
|
1038
|
+
|
|
1039
|
+
Pre-trends tests have low power and can exacerbate bias. **Honest DiD** (Rambachan & Roth 2023) provides sensitivity analysis showing how robust your results are to violations of parallel trends.
|
|
1040
|
+
|
|
1041
|
+
```python
|
|
1042
|
+
from diff_diff import HonestDiD, MultiPeriodDiD
|
|
1043
|
+
|
|
1044
|
+
# First, fit a standard event study
|
|
1045
|
+
did = MultiPeriodDiD()
|
|
1046
|
+
event_results = did.fit(
|
|
1047
|
+
data,
|
|
1048
|
+
outcome='outcome',
|
|
1049
|
+
treatment='treated',
|
|
1050
|
+
time='period',
|
|
1051
|
+
post_periods=[5, 6, 7, 8, 9]
|
|
1052
|
+
)
|
|
1053
|
+
|
|
1054
|
+
# Compute honest bounds with relative magnitudes restriction
|
|
1055
|
+
# M=1 means post-treatment violations can be up to 1x the worst pre-treatment violation
|
|
1056
|
+
honest = HonestDiD(method='relative_magnitude', M=1.0)
|
|
1057
|
+
honest_results = honest.fit(event_results)
|
|
1058
|
+
|
|
1059
|
+
print(honest_results.summary())
|
|
1060
|
+
print(f"Original estimate: {honest_results.original_estimate:.4f}")
|
|
1061
|
+
print(f"Robust 95% CI: [{honest_results.ci_lb:.4f}, {honest_results.ci_ub:.4f}]")
|
|
1062
|
+
print(f"Effect robust to violations: {honest_results.is_significant}")
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
**Sensitivity analysis over M values:**
|
|
1066
|
+
|
|
1067
|
+
```python
|
|
1068
|
+
# How do results change as we allow larger violations?
|
|
1069
|
+
sensitivity = honest.sensitivity_analysis(
|
|
1070
|
+
event_results,
|
|
1071
|
+
M_grid=[0, 0.5, 1.0, 1.5, 2.0]
|
|
1072
|
+
)
|
|
1073
|
+
|
|
1074
|
+
print(sensitivity.summary())
|
|
1075
|
+
print(f"Breakdown value: M = {sensitivity.breakdown_M}")
|
|
1076
|
+
# Breakdown = smallest M where the robust CI includes zero
|
|
1077
|
+
```
|
|
1078
|
+
|
|
1079
|
+
**Breakdown value:**
|
|
1080
|
+
|
|
1081
|
+
The breakdown value tells you how robust your conclusion is:
|
|
1082
|
+
|
|
1083
|
+
```python
|
|
1084
|
+
breakdown = honest.breakdown_value(event_results)
|
|
1085
|
+
if breakdown >= 1.0:
|
|
1086
|
+
print("Result holds even if post-treatment violations are as bad as pre-treatment")
|
|
1087
|
+
else:
|
|
1088
|
+
print(f"Result requires violations smaller than {breakdown:.1f}x pre-treatment")
|
|
1089
|
+
```
|
|
1090
|
+
|
|
1091
|
+
**Smoothness restriction (alternative approach):**
|
|
1092
|
+
|
|
1093
|
+
```python
|
|
1094
|
+
# Bounds second differences of trend violations
|
|
1095
|
+
# M=0 means linear extrapolation of pre-trends
|
|
1096
|
+
honest_smooth = HonestDiD(method='smoothness', M=0.5)
|
|
1097
|
+
smooth_results = honest_smooth.fit(event_results)
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
**Visualization:**
|
|
1101
|
+
|
|
1102
|
+
```python
|
|
1103
|
+
from diff_diff import plot_sensitivity, plot_honest_event_study
|
|
1104
|
+
|
|
1105
|
+
# Plot sensitivity analysis
|
|
1106
|
+
plot_sensitivity(sensitivity, title="Sensitivity to Parallel Trends Violations")
|
|
1107
|
+
|
|
1108
|
+
# Event study with honest confidence intervals
|
|
1109
|
+
plot_honest_event_study(event_results, honest_results)
|
|
1110
|
+
```
|
|
1111
|
+
|
|
1112
|
+
### Placebo Tests
|
|
1113
|
+
|
|
1114
|
+
Placebo tests help validate the parallel trends assumption by checking whether effects appear where they shouldn't (before treatment or in untreated groups).
|
|
1115
|
+
|
|
1116
|
+
**Fake timing test:**
|
|
1117
|
+
|
|
1118
|
+
```python
|
|
1119
|
+
from diff_diff import run_placebo_test
|
|
1120
|
+
|
|
1121
|
+
# Test: Is there an effect before treatment actually occurred?
|
|
1122
|
+
# Actual treatment is at period 3 (post_periods=[3, 4, 5])
|
|
1123
|
+
# We test if a "fake" treatment at period 1 shows an effect
|
|
1124
|
+
results = run_placebo_test(
|
|
1125
|
+
data,
|
|
1126
|
+
outcome='outcome',
|
|
1127
|
+
treatment='treated',
|
|
1128
|
+
time='period',
|
|
1129
|
+
test_type='fake_timing',
|
|
1130
|
+
fake_treatment_period=1, # Pretend treatment was in period 1
|
|
1131
|
+
post_periods=[3, 4, 5] # Actual post-treatment periods
|
|
1132
|
+
)
|
|
1133
|
+
|
|
1134
|
+
print(results.summary())
|
|
1135
|
+
# If parallel trends hold, placebo_effect should be ~0 and not significant
|
|
1136
|
+
print(f"Placebo effect: {results.placebo_effect:.3f} (p={results.p_value:.3f})")
|
|
1137
|
+
print(f"Is significant (bad): {results.is_significant}")
|
|
1138
|
+
```
|
|
1139
|
+
|
|
1140
|
+
**Fake group test:**
|
|
1141
|
+
|
|
1142
|
+
```python
|
|
1143
|
+
# Test: Is there an effect among never-treated units?
|
|
1144
|
+
# Get some control unit IDs to use as "fake treated"
|
|
1145
|
+
control_units = data[data['treated'] == 0]['firm_id'].unique()[:5]
|
|
1146
|
+
|
|
1147
|
+
results = run_placebo_test(
|
|
1148
|
+
data,
|
|
1149
|
+
outcome='outcome',
|
|
1150
|
+
treatment='treated',
|
|
1151
|
+
time='period',
|
|
1152
|
+
unit='firm_id',
|
|
1153
|
+
test_type='fake_group',
|
|
1154
|
+
fake_treatment_group=list(control_units), # List of control unit IDs
|
|
1155
|
+
post_periods=[3, 4, 5]
|
|
1156
|
+
)
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
**Permutation test:**
|
|
1160
|
+
|
|
1161
|
+
```python
|
|
1162
|
+
# Randomly reassign treatment and compute distribution of effects
|
|
1163
|
+
# Note: requires binary post indicator (use 'post' column, not 'period')
|
|
1164
|
+
results = run_placebo_test(
|
|
1165
|
+
data,
|
|
1166
|
+
outcome='outcome',
|
|
1167
|
+
treatment='treated',
|
|
1168
|
+
time='post', # Binary post-treatment indicator
|
|
1169
|
+
unit='firm_id',
|
|
1170
|
+
test_type='permutation',
|
|
1171
|
+
n_permutations=1000,
|
|
1172
|
+
seed=42
|
|
1173
|
+
)
|
|
1174
|
+
|
|
1175
|
+
print(f"Original effect: {results.original_effect:.3f}")
|
|
1176
|
+
print(f"Permutation p-value: {results.p_value:.4f}")
|
|
1177
|
+
# Low p-value indicates the effect is unlikely to be due to chance
|
|
1178
|
+
```
|
|
1179
|
+
|
|
1180
|
+
**Leave-one-out sensitivity:**
|
|
1181
|
+
|
|
1182
|
+
```python
|
|
1183
|
+
# Test sensitivity to individual treated units
|
|
1184
|
+
# Note: requires binary post indicator (use 'post' column, not 'period')
|
|
1185
|
+
results = run_placebo_test(
|
|
1186
|
+
data,
|
|
1187
|
+
outcome='outcome',
|
|
1188
|
+
treatment='treated',
|
|
1189
|
+
time='post', # Binary post-treatment indicator
|
|
1190
|
+
unit='firm_id',
|
|
1191
|
+
test_type='leave_one_out'
|
|
1192
|
+
)
|
|
1193
|
+
|
|
1194
|
+
# Check if any single unit drives the result
|
|
1195
|
+
print(results.leave_one_out_effects) # Effect when each unit is dropped
|
|
1196
|
+
```
|
|
1197
|
+
|
|
1198
|
+
**Run all placebo tests:**
|
|
1199
|
+
|
|
1200
|
+
```python
|
|
1201
|
+
from diff_diff import run_all_placebo_tests
|
|
1202
|
+
|
|
1203
|
+
# Comprehensive diagnostic suite
|
|
1204
|
+
# Note: This function runs fake_timing tests on pre-treatment periods.
|
|
1205
|
+
# The permutation and leave_one_out tests require a binary post indicator,
|
|
1206
|
+
# so they may return errors if the data uses multi-period time column.
|
|
1207
|
+
all_results = run_all_placebo_tests(
|
|
1208
|
+
data,
|
|
1209
|
+
outcome='outcome',
|
|
1210
|
+
treatment='treated',
|
|
1211
|
+
time='period',
|
|
1212
|
+
unit='firm_id',
|
|
1213
|
+
pre_periods=[0, 1, 2],
|
|
1214
|
+
post_periods=[3, 4, 5],
|
|
1215
|
+
n_permutations=500,
|
|
1216
|
+
seed=42
|
|
1217
|
+
)
|
|
1218
|
+
|
|
1219
|
+
for test_name, result in all_results.items():
|
|
1220
|
+
if hasattr(result, 'p_value'):
|
|
1221
|
+
print(f"{test_name}: p={result.p_value:.3f}, significant={result.is_significant}")
|
|
1222
|
+
elif isinstance(result, dict) and 'error' in result:
|
|
1223
|
+
print(f"{test_name}: Error - {result['error']}")
|
|
1224
|
+
```
|
|
1225
|
+
|
|
975
1226
|
## API Reference
|
|
976
1227
|
|
|
977
1228
|
### DifferenceInDifferences
|
|
@@ -1156,6 +1407,75 @@ SyntheticDiD(
|
|
|
1156
1407
|
| `get_unit_weights_df()` | Get unit weights as DataFrame |
|
|
1157
1408
|
| `get_time_weights_df()` | Get time weights as DataFrame |
|
|
1158
1409
|
|
|
1410
|
+
### HonestDiD
|
|
1411
|
+
|
|
1412
|
+
```python
|
|
1413
|
+
HonestDiD(
|
|
1414
|
+
method='relative_magnitude', # 'relative_magnitude' or 'smoothness'
|
|
1415
|
+
M=None, # Restriction parameter (default: 1.0 for RM, 0.0 for SD)
|
|
1416
|
+
alpha=0.05, # Significance level for CIs
|
|
1417
|
+
l_vec=None # Linear combination vector for target parameter
|
|
1418
|
+
)
|
|
1419
|
+
```
|
|
1420
|
+
|
|
1421
|
+
**fit() Parameters:**
|
|
1422
|
+
|
|
1423
|
+
| Parameter | Type | Description |
|
|
1424
|
+
|-----------|------|-------------|
|
|
1425
|
+
| `results` | MultiPeriodDiDResults | Results from MultiPeriodDiD.fit() |
|
|
1426
|
+
| `M` | float | Restriction parameter (overrides constructor value) |
|
|
1427
|
+
|
|
1428
|
+
**Methods:**
|
|
1429
|
+
|
|
1430
|
+
| Method | Description |
|
|
1431
|
+
|--------|-------------|
|
|
1432
|
+
| `fit(results, M)` | Compute bounds for given event study results |
|
|
1433
|
+
| `sensitivity_analysis(results, M_grid)` | Compute bounds over grid of M values |
|
|
1434
|
+
| `breakdown_value(results, tol)` | Find smallest M where CI includes zero |
|
|
1435
|
+
|
|
1436
|
+
### HonestDiDResults
|
|
1437
|
+
|
|
1438
|
+
**Attributes:**
|
|
1439
|
+
|
|
1440
|
+
| Attribute | Description |
|
|
1441
|
+
|-----------|-------------|
|
|
1442
|
+
| `original_estimate` | Point estimate under parallel trends |
|
|
1443
|
+
| `lb` | Lower bound of identified set |
|
|
1444
|
+
| `ub` | Upper bound of identified set |
|
|
1445
|
+
| `ci_lb` | Lower bound of robust confidence interval |
|
|
1446
|
+
| `ci_ub` | Upper bound of robust confidence interval |
|
|
1447
|
+
| `ci_width` | Width of robust CI |
|
|
1448
|
+
| `M` | Restriction parameter used |
|
|
1449
|
+
| `method` | Restriction method ('relative_magnitude' or 'smoothness') |
|
|
1450
|
+
| `alpha` | Significance level |
|
|
1451
|
+
| `is_significant` | True if robust CI excludes zero |
|
|
1452
|
+
|
|
1453
|
+
**Methods:**
|
|
1454
|
+
|
|
1455
|
+
| Method | Description |
|
|
1456
|
+
|--------|-------------|
|
|
1457
|
+
| `summary()` | Get formatted summary string |
|
|
1458
|
+
| `to_dict()` | Convert to dictionary |
|
|
1459
|
+
| `to_dataframe()` | Convert to pandas DataFrame |
|
|
1460
|
+
|
|
1461
|
+
### SensitivityResults
|
|
1462
|
+
|
|
1463
|
+
**Attributes:**
|
|
1464
|
+
|
|
1465
|
+
| Attribute | Description |
|
|
1466
|
+
|-----------|-------------|
|
|
1467
|
+
| `M_grid` | Array of M values analyzed |
|
|
1468
|
+
| `results` | List of HonestDiDResults for each M |
|
|
1469
|
+
| `breakdown_M` | Smallest M where CI includes zero (None if always significant) |
|
|
1470
|
+
|
|
1471
|
+
**Methods:**
|
|
1472
|
+
|
|
1473
|
+
| Method | Description |
|
|
1474
|
+
|--------|-------------|
|
|
1475
|
+
| `summary()` | Get formatted summary string |
|
|
1476
|
+
| `plot(ax)` | Plot sensitivity analysis |
|
|
1477
|
+
| `to_dataframe()` | Convert to pandas DataFrame |
|
|
1478
|
+
|
|
1159
1479
|
### Data Preparation Functions
|
|
1160
1480
|
|
|
1161
1481
|
#### generate_did_data
|
|
@@ -1351,6 +1671,18 @@ This library implements methods from the following scholarly works:
|
|
|
1351
1671
|
|
|
1352
1672
|
- **Cameron, A. C., Gelbach, J. B., & Miller, D. L. (2011).** "Robust Inference With Multiway Clustering." *Journal of Business & Economic Statistics*, 29(2), 238-249. [https://doi.org/10.1198/jbes.2010.07136](https://doi.org/10.1198/jbes.2010.07136)
|
|
1353
1673
|
|
|
1674
|
+
### Wild Cluster Bootstrap
|
|
1675
|
+
|
|
1676
|
+
- **Cameron, A. C., Gelbach, J. B., & Miller, D. L. (2008).** "Bootstrap-Based Improvements for Inference with Clustered Errors." *The Review of Economics and Statistics*, 90(3), 414-427. [https://doi.org/10.1162/rest.90.3.414](https://doi.org/10.1162/rest.90.3.414)
|
|
1677
|
+
|
|
1678
|
+
- **Webb, M. D. (2014).** "Reworking Wild Bootstrap Based Inference for Clustered Errors." Queen's Economics Department Working Paper No. 1315. [https://www.econ.queensu.ca/sites/econ.queensu.ca/files/qed_wp_1315.pdf](https://www.econ.queensu.ca/sites/econ.queensu.ca/files/qed_wp_1315.pdf)
|
|
1679
|
+
|
|
1680
|
+
- **MacKinnon, J. G., & Webb, M. D. (2018).** "The Wild Bootstrap for Few (Treated) Clusters." *The Econometrics Journal*, 21(2), 114-135. [https://doi.org/10.1111/ectj.12107](https://doi.org/10.1111/ectj.12107)
|
|
1681
|
+
|
|
1682
|
+
### Placebo Tests and DiD Diagnostics
|
|
1683
|
+
|
|
1684
|
+
- **Bertrand, M., Duflo, E., & Mullainathan, S. (2004).** "How Much Should We Trust Differences-in-Differences Estimates?" *The Quarterly Journal of Economics*, 119(1), 249-275. [https://doi.org/10.1162/003355304772839588](https://doi.org/10.1162/003355304772839588)
|
|
1685
|
+
|
|
1354
1686
|
### Synthetic Control Method
|
|
1355
1687
|
|
|
1356
1688
|
- **Abadie, A., & Gardeazabal, J. (2003).** "The Economic Costs of Conflict: A Case Study of the Basque Country." *The American Economic Review*, 93(1), 113-132. [https://doi.org/10.1257/000282803321455188](https://doi.org/10.1257/000282803321455188)
|
|
@@ -1367,9 +1699,23 @@ This library implements methods from the following scholarly works:
|
|
|
1367
1699
|
|
|
1368
1700
|
- **Roth, J. (2022).** "Pretest with Caution: Event-Study Estimates after Testing for Parallel Trends." *American Economic Review: Insights*, 4(3), 305-322. [https://doi.org/10.1257/aeri.20210236](https://doi.org/10.1257/aeri.20210236)
|
|
1369
1701
|
|
|
1702
|
+
- **Lakens, D. (2017).** "Equivalence Tests: A Practical Primer for t Tests, Correlations, and Meta-Analyses." *Social Psychological and Personality Science*, 8(4), 355-362. [https://doi.org/10.1177/1948550617697177](https://doi.org/10.1177/1948550617697177)
|
|
1703
|
+
|
|
1704
|
+
### Honest DiD / Sensitivity Analysis
|
|
1705
|
+
|
|
1706
|
+
The `HonestDiD` module implements sensitivity analysis methods for relaxing the parallel trends assumption:
|
|
1707
|
+
|
|
1370
1708
|
- **Rambachan, A., & Roth, J. (2023).** "A More Credible Approach to Parallel Trends." *The Review of Economic Studies*, 90(5), 2555-2591. [https://doi.org/10.1093/restud/rdad018](https://doi.org/10.1093/restud/rdad018)
|
|
1371
1709
|
|
|
1372
|
-
|
|
1710
|
+
This paper introduces the "Honest DiD" framework implemented in our `HonestDiD` class:
|
|
1711
|
+
- **Relative Magnitudes (ΔRM)**: Bounds post-treatment violations by a multiple of observed pre-treatment violations
|
|
1712
|
+
- **Smoothness (ΔSD)**: Bounds on second differences of trend violations, allowing for linear extrapolation of pre-trends
|
|
1713
|
+
- **Breakdown Analysis**: Finding the smallest violation magnitude that would overturn conclusions
|
|
1714
|
+
- **Robust Confidence Intervals**: Valid inference under partial identification
|
|
1715
|
+
|
|
1716
|
+
- **Roth, J., & Sant'Anna, P. H. C. (2023).** "When Is Parallel Trends Sensitive to Functional Form?" *Econometrica*, 91(2), 737-747. [https://doi.org/10.3982/ECTA19402](https://doi.org/10.3982/ECTA19402)
|
|
1717
|
+
|
|
1718
|
+
Discusses functional form sensitivity in parallel trends assumptions, relevant to understanding when smoothness restrictions are appropriate.
|
|
1373
1719
|
|
|
1374
1720
|
### Multi-Period and Staggered Adoption
|
|
1375
1721
|
|