diff-diff 1.0.0__tar.gz → 1.2.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-1.0.0 → diff_diff-1.2.0}/PKG-INFO +332 -6
- {diff_diff-1.0.0 → diff_diff-1.2.0}/README.md +331 -5
- {diff_diff-1.0.0 → diff_diff-1.2.0}/diff_diff/__init__.py +24 -1
- {diff_diff-1.0.0 → diff_diff-1.2.0}/diff_diff/diagnostics.py +62 -23
- {diff_diff-1.0.0 → diff_diff-1.2.0}/diff_diff/estimators.py +77 -875
- {diff_diff-1.0.0 → diff_diff-1.2.0}/diff_diff/honest_did.py +4 -2
- {diff_diff-1.0.0 → diff_diff-1.2.0}/diff_diff/power.py +1 -1
- {diff_diff-1.0.0 → diff_diff-1.2.0}/diff_diff/prep.py +1 -1
- diff_diff-1.2.0/diff_diff/pretrends.py +1067 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/diff_diff/staggered.py +52 -21
- diff_diff-1.2.0/diff_diff/sun_abraham.py +1198 -0
- diff_diff-1.2.0/diff_diff/synthetic_did.py +564 -0
- diff_diff-1.2.0/diff_diff/twfe.py +344 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/diff_diff/utils.py +1 -1
- {diff_diff-1.0.0 → diff_diff-1.2.0}/diff_diff/visualization.py +240 -1
- {diff_diff-1.0.0 → diff_diff-1.2.0}/diff_diff.egg-info/PKG-INFO +332 -6
- {diff_diff-1.0.0 → diff_diff-1.2.0}/diff_diff.egg-info/SOURCES.txt +6 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/pyproject.toml +1 -1
- diff_diff-1.2.0/tests/test_pretrends.py +813 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/tests/test_staggered.py +9 -3
- diff_diff-1.2.0/tests/test_sun_abraham.py +732 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/diff_diff/bacon.py +0 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/diff_diff/results.py +0 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/diff_diff.egg-info/dependency_links.txt +0 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/diff_diff.egg-info/requires.txt +0 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/diff_diff.egg-info/top_level.txt +0 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/setup.cfg +0 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/tests/test_bacon.py +0 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/tests/test_diagnostics.py +0 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/tests/test_estimators.py +0 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/tests/test_honest_did.py +0 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/tests/test_power.py +0 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/tests/test_prep.py +0 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/tests/test_utils.py +0 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/tests/test_visualization.py +0 -0
- {diff_diff-1.0.0 → diff_diff-1.2.0}/tests/test_wild_bootstrap.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: diff-diff
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: A library for Difference-in-Differences causal inference analysis
|
|
5
5
|
Author: diff-diff contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -105,13 +105,14 @@ Signif. codes: '***' 0.001, '**' 0.01, '*' 0.05, '.' 0.1
|
|
|
105
105
|
- **Wild cluster bootstrap**: Valid inference with few clusters (<50) using Rademacher, Webb, or Mammen weights
|
|
106
106
|
- **Panel data support**: Two-way fixed effects estimator for panel designs
|
|
107
107
|
- **Multi-period analysis**: Event-study style DiD with period-specific treatment effects
|
|
108
|
-
- **Staggered adoption**: Callaway-Sant'Anna (2021)
|
|
108
|
+
- **Staggered adoption**: Callaway-Sant'Anna (2021) and Sun-Abraham (2021) estimators for heterogeneous treatment timing
|
|
109
109
|
- **Synthetic DiD**: Combined DiD with synthetic control for improved robustness
|
|
110
110
|
- **Event study plots**: Publication-ready visualization of treatment effects
|
|
111
111
|
- **Parallel trends testing**: Multiple methods including equivalence tests
|
|
112
112
|
- **Goodman-Bacon decomposition**: Diagnose TWFE bias by decomposing into 2x2 comparisons
|
|
113
113
|
- **Placebo tests**: Comprehensive diagnostics including fake timing, fake group, permutation, and leave-one-out tests
|
|
114
114
|
- **Honest DiD sensitivity analysis**: Rambachan-Roth (2023) bounds and breakdown analysis for parallel trends violations
|
|
115
|
+
- **Pre-trends power analysis**: Roth (2022) minimum detectable violation (MDV) and power curves for pre-trends tests
|
|
115
116
|
- **Power analysis**: MDE, sample size, and power calculations for study design; simulation-based power for any estimator
|
|
116
117
|
- **Data prep utilities**: Helper functions for common data preparation tasks
|
|
117
118
|
|
|
@@ -122,7 +123,7 @@ We provide Jupyter notebook tutorials in `docs/tutorials/`:
|
|
|
122
123
|
| Notebook | Description |
|
|
123
124
|
|----------|-------------|
|
|
124
125
|
| `01_basic_did.ipynb` | Basic 2x2 DiD, formula interface, covariates, fixed effects, cluster-robust SE, wild bootstrap |
|
|
125
|
-
| `02_staggered_did.ipynb` | Staggered adoption with Callaway-Sant'Anna, group-time effects, aggregation methods, Bacon decomposition |
|
|
126
|
+
| `02_staggered_did.ipynb` | Staggered adoption with Callaway-Sant'Anna and Sun-Abraham, group-time effects, aggregation methods, Bacon decomposition |
|
|
126
127
|
| `03_synthetic_did.ipynb` | Synthetic DiD, unit/time weights, inference methods, regularization |
|
|
127
128
|
| `04_parallel_trends.ipynb` | Testing parallel trends, equivalence tests, placebo tests, diagnostics |
|
|
128
129
|
| `05_honest_did.ipynb` | Honest DiD sensitivity analysis, bounds, breakdown values, visualization |
|
|
@@ -574,7 +575,7 @@ Works with `DifferenceInDifferences` and `TwoWayFixedEffects` estimators.
|
|
|
574
575
|
### Two-Way Fixed Effects (Panel Data)
|
|
575
576
|
|
|
576
577
|
```python
|
|
577
|
-
from diff_diff
|
|
578
|
+
from diff_diff import TwoWayFixedEffects
|
|
578
579
|
|
|
579
580
|
twfe = TwoWayFixedEffects()
|
|
580
581
|
results = twfe.fit(
|
|
@@ -797,12 +798,115 @@ results = cs.fit(
|
|
|
797
798
|
)
|
|
798
799
|
```
|
|
799
800
|
|
|
801
|
+
### Sun-Abraham Interaction-Weighted Estimator
|
|
802
|
+
|
|
803
|
+
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.
|
|
804
|
+
|
|
805
|
+
```python
|
|
806
|
+
from diff_diff import SunAbraham
|
|
807
|
+
|
|
808
|
+
# Basic usage
|
|
809
|
+
sa = SunAbraham()
|
|
810
|
+
results = sa.fit(
|
|
811
|
+
panel_data,
|
|
812
|
+
outcome='sales',
|
|
813
|
+
unit='firm_id',
|
|
814
|
+
time='year',
|
|
815
|
+
first_treat='first_treat' # 0 for never-treated, else first treatment year
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
# View results
|
|
819
|
+
results.print_summary()
|
|
820
|
+
|
|
821
|
+
# Event study effects (by relative time to treatment)
|
|
822
|
+
for rel_time, effect in results.event_study_effects.items():
|
|
823
|
+
print(f"e={rel_time}: {effect['effect']:.3f} (SE: {effect['se']:.3f})")
|
|
824
|
+
|
|
825
|
+
# Overall ATT
|
|
826
|
+
print(f"Overall ATT: {results.overall_att:.3f} (SE: {results.overall_se:.3f})")
|
|
827
|
+
|
|
828
|
+
# Cohort weights (how each cohort contributes to each event-time estimate)
|
|
829
|
+
for rel_time, weights in results.cohort_weights.items():
|
|
830
|
+
print(f"e={rel_time}: {weights}")
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
**Parameters:**
|
|
834
|
+
|
|
835
|
+
```python
|
|
836
|
+
SunAbraham(
|
|
837
|
+
control_group='never_treated', # or 'not_yet_treated'
|
|
838
|
+
anticipation=0, # Periods before treatment with effects
|
|
839
|
+
alpha=0.05, # Significance level
|
|
840
|
+
cluster=None, # Column for cluster SEs
|
|
841
|
+
n_bootstrap=0, # Bootstrap iterations (0 = analytical SEs)
|
|
842
|
+
bootstrap_weights='rademacher', # 'rademacher', 'mammen', or 'webb'
|
|
843
|
+
seed=None # Random seed
|
|
844
|
+
)
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
**Bootstrap inference:**
|
|
848
|
+
|
|
849
|
+
```python
|
|
850
|
+
# Bootstrap inference with 999 iterations
|
|
851
|
+
sa = SunAbraham(
|
|
852
|
+
n_bootstrap=999,
|
|
853
|
+
bootstrap_weights='rademacher',
|
|
854
|
+
seed=42
|
|
855
|
+
)
|
|
856
|
+
results = sa.fit(
|
|
857
|
+
data,
|
|
858
|
+
outcome='sales',
|
|
859
|
+
unit='firm_id',
|
|
860
|
+
time='year',
|
|
861
|
+
first_treat='first_treat'
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
# Access bootstrap results
|
|
865
|
+
print(f"Overall ATT: {results.overall_att:.3f}")
|
|
866
|
+
print(f"Bootstrap SE: {results.bootstrap_results.overall_att_se:.3f}")
|
|
867
|
+
print(f"Bootstrap 95% CI: {results.bootstrap_results.overall_att_ci}")
|
|
868
|
+
print(f"Bootstrap p-value: {results.bootstrap_results.overall_att_p_value:.4f}")
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
**When to use Sun-Abraham vs Callaway-Sant'Anna:**
|
|
872
|
+
|
|
873
|
+
| Aspect | Sun-Abraham | Callaway-Sant'Anna |
|
|
874
|
+
|--------|-------------|-------------------|
|
|
875
|
+
| Approach | Interaction-weighted regression | 2x2 DiD aggregation |
|
|
876
|
+
| Efficiency | More efficient under homogeneous effects | More robust to heterogeneity |
|
|
877
|
+
| Weighting | Weights by cohort share at each relative time | Weights by sample size |
|
|
878
|
+
| Use case | Robustness check, regression-based inference | Primary staggered DiD estimator |
|
|
879
|
+
|
|
880
|
+
**Both estimators should give similar results when:**
|
|
881
|
+
- Treatment effects are relatively homogeneous across cohorts
|
|
882
|
+
- Parallel trends holds
|
|
883
|
+
|
|
884
|
+
**Running both as robustness check:**
|
|
885
|
+
|
|
886
|
+
```python
|
|
887
|
+
from diff_diff import CallawaySantAnna, SunAbraham
|
|
888
|
+
|
|
889
|
+
# Callaway-Sant'Anna
|
|
890
|
+
cs = CallawaySantAnna()
|
|
891
|
+
cs_results = cs.fit(data, outcome='y', unit='unit', time='time', first_treat='first_treat')
|
|
892
|
+
|
|
893
|
+
# Sun-Abraham
|
|
894
|
+
sa = SunAbraham()
|
|
895
|
+
sa_results = sa.fit(data, outcome='y', unit='unit', time='time', first_treat='first_treat')
|
|
896
|
+
|
|
897
|
+
# Compare
|
|
898
|
+
print(f"Callaway-Sant'Anna ATT: {cs_results.overall_att:.3f}")
|
|
899
|
+
print(f"Sun-Abraham ATT: {sa_results.overall_att:.3f}")
|
|
900
|
+
|
|
901
|
+
# If results differ substantially, investigate heterogeneity
|
|
902
|
+
```
|
|
903
|
+
|
|
800
904
|
### Event Study Visualization
|
|
801
905
|
|
|
802
906
|
Create publication-ready event study plots:
|
|
803
907
|
|
|
804
908
|
```python
|
|
805
|
-
from diff_diff import plot_event_study, MultiPeriodDiD, CallawaySantAnna
|
|
909
|
+
from diff_diff import plot_event_study, MultiPeriodDiD, CallawaySantAnna, SunAbraham
|
|
806
910
|
|
|
807
911
|
# From MultiPeriodDiD
|
|
808
912
|
did = MultiPeriodDiD()
|
|
@@ -814,7 +918,13 @@ plot_event_study(results, title="Treatment Effects Over Time")
|
|
|
814
918
|
cs = CallawaySantAnna()
|
|
815
919
|
results = cs.fit(data, outcome='y', unit='unit', time='period',
|
|
816
920
|
first_treat='first_treat', aggregate='event_study')
|
|
817
|
-
plot_event_study(results, title="Staggered DiD Event Study")
|
|
921
|
+
plot_event_study(results, title="Staggered DiD Event Study (CS)")
|
|
922
|
+
|
|
923
|
+
# From SunAbraham
|
|
924
|
+
sa = SunAbraham()
|
|
925
|
+
results = sa.fit(data, outcome='y', unit='unit', time='period',
|
|
926
|
+
first_treat='first_treat')
|
|
927
|
+
plot_event_study(results, title="Staggered DiD Event Study (SA)")
|
|
818
928
|
|
|
819
929
|
# From a DataFrame
|
|
820
930
|
df = pd.DataFrame({
|
|
@@ -1147,6 +1257,90 @@ plot_sensitivity(sensitivity, title="Sensitivity to Parallel Trends Violations")
|
|
|
1147
1257
|
plot_honest_event_study(event_results, honest_results)
|
|
1148
1258
|
```
|
|
1149
1259
|
|
|
1260
|
+
### Pre-Trends Power Analysis (Roth 2022)
|
|
1261
|
+
|
|
1262
|
+
A passing pre-trends test doesn't mean parallel trends holds—it may just mean the test has low power. **Pre-Trends Power Analysis** (Roth 2022) answers: "What violations could my pre-trends test have detected?"
|
|
1263
|
+
|
|
1264
|
+
```python
|
|
1265
|
+
from diff_diff import PreTrendsPower, MultiPeriodDiD
|
|
1266
|
+
|
|
1267
|
+
# First, fit an event study
|
|
1268
|
+
did = MultiPeriodDiD()
|
|
1269
|
+
event_results = did.fit(
|
|
1270
|
+
data,
|
|
1271
|
+
outcome='outcome',
|
|
1272
|
+
treatment='treated',
|
|
1273
|
+
time='period',
|
|
1274
|
+
post_periods=[5, 6, 7, 8, 9]
|
|
1275
|
+
)
|
|
1276
|
+
|
|
1277
|
+
# Analyze pre-trends test power
|
|
1278
|
+
pt = PreTrendsPower(alpha=0.05, power=0.80)
|
|
1279
|
+
power_results = pt.fit(event_results)
|
|
1280
|
+
|
|
1281
|
+
print(power_results.summary())
|
|
1282
|
+
print(f"Minimum Detectable Violation (MDV): {power_results.mdv:.4f}")
|
|
1283
|
+
print(f"Power to detect violations of size MDV: {power_results.power:.1%}")
|
|
1284
|
+
```
|
|
1285
|
+
|
|
1286
|
+
**Key concepts:**
|
|
1287
|
+
|
|
1288
|
+
- **Minimum Detectable Violation (MDV)**: Smallest violation magnitude that would be detected with your target power (e.g., 80%). Passing the pre-trends test does NOT rule out violations up to this size.
|
|
1289
|
+
- **Power**: Probability of detecting a violation of given size if it exists.
|
|
1290
|
+
- **Violation types**: Linear trend, constant violation, last-period only, or custom patterns.
|
|
1291
|
+
|
|
1292
|
+
**Power curve visualization:**
|
|
1293
|
+
|
|
1294
|
+
```python
|
|
1295
|
+
from diff_diff import plot_pretrends_power
|
|
1296
|
+
|
|
1297
|
+
# Generate power curve across violation magnitudes
|
|
1298
|
+
curve = pt.power_curve(event_results)
|
|
1299
|
+
|
|
1300
|
+
# Plot the power curve
|
|
1301
|
+
plot_pretrends_power(curve, title="Pre-Trends Test Power Curve")
|
|
1302
|
+
|
|
1303
|
+
# Or from the curve object directly
|
|
1304
|
+
curve.plot()
|
|
1305
|
+
```
|
|
1306
|
+
|
|
1307
|
+
**Different violation patterns:**
|
|
1308
|
+
|
|
1309
|
+
```python
|
|
1310
|
+
# Linear trend violations (default) - most common assumption
|
|
1311
|
+
pt_linear = PreTrendsPower(violation_type='linear')
|
|
1312
|
+
|
|
1313
|
+
# Constant violation in all pre-periods
|
|
1314
|
+
pt_constant = PreTrendsPower(violation_type='constant')
|
|
1315
|
+
|
|
1316
|
+
# Violation only in the last pre-period (sharp break)
|
|
1317
|
+
pt_last = PreTrendsPower(violation_type='last_period')
|
|
1318
|
+
|
|
1319
|
+
# Custom violation pattern
|
|
1320
|
+
custom_weights = np.array([0.1, 0.3, 0.6]) # Increasing violations
|
|
1321
|
+
pt_custom = PreTrendsPower(violation_type='custom', violation_weights=custom_weights)
|
|
1322
|
+
```
|
|
1323
|
+
|
|
1324
|
+
**Combining with HonestDiD:**
|
|
1325
|
+
|
|
1326
|
+
Pre-trends power analysis and HonestDiD are complementary:
|
|
1327
|
+
1. **Pre-trends power** tells you what the test could have detected
|
|
1328
|
+
2. **HonestDiD** tells you how robust your results are to violations
|
|
1329
|
+
|
|
1330
|
+
```python
|
|
1331
|
+
from diff_diff import HonestDiD, PreTrendsPower
|
|
1332
|
+
|
|
1333
|
+
# If MDV is large relative to your estimated effect, be cautious
|
|
1334
|
+
pt = PreTrendsPower()
|
|
1335
|
+
power_results = pt.fit(event_results)
|
|
1336
|
+
sensitivity = pt.sensitivity_to_honest_did(event_results)
|
|
1337
|
+
print(sensitivity['interpretation'])
|
|
1338
|
+
|
|
1339
|
+
# Use HonestDiD for robust inference
|
|
1340
|
+
honest = HonestDiD(method='relative_magnitude', M=1.0)
|
|
1341
|
+
honest_results = honest.fit(event_results)
|
|
1342
|
+
```
|
|
1343
|
+
|
|
1150
1344
|
### Placebo Tests
|
|
1151
1345
|
|
|
1152
1346
|
Placebo tests help validate the parallel trends assumption by checking whether effects appear where they shouldn't (before treatment or in untreated groups).
|
|
@@ -1445,6 +1639,63 @@ SyntheticDiD(
|
|
|
1445
1639
|
| `get_unit_weights_df()` | Get unit weights as DataFrame |
|
|
1446
1640
|
| `get_time_weights_df()` | Get time weights as DataFrame |
|
|
1447
1641
|
|
|
1642
|
+
### SunAbraham
|
|
1643
|
+
|
|
1644
|
+
```python
|
|
1645
|
+
SunAbraham(
|
|
1646
|
+
control_group='never_treated', # or 'not_yet_treated'
|
|
1647
|
+
anticipation=0, # Periods of anticipation effects
|
|
1648
|
+
alpha=0.05, # Significance level for CIs
|
|
1649
|
+
cluster=None, # Column for cluster-robust SEs
|
|
1650
|
+
n_bootstrap=0, # Bootstrap iterations (0 = analytical SEs)
|
|
1651
|
+
bootstrap_weights='rademacher', # 'rademacher', 'mammen', or 'webb'
|
|
1652
|
+
seed=None # Random seed
|
|
1653
|
+
)
|
|
1654
|
+
```
|
|
1655
|
+
|
|
1656
|
+
**fit() Parameters:**
|
|
1657
|
+
|
|
1658
|
+
| Parameter | Type | Description |
|
|
1659
|
+
|-----------|------|-------------|
|
|
1660
|
+
| `data` | DataFrame | Panel data |
|
|
1661
|
+
| `outcome` | str | Outcome variable column name |
|
|
1662
|
+
| `unit` | str | Unit identifier column |
|
|
1663
|
+
| `time` | str | Time period column |
|
|
1664
|
+
| `first_treat` | str | Column with first treatment period (0 for never-treated) |
|
|
1665
|
+
| `covariates` | list | Covariate column names |
|
|
1666
|
+
| `min_pre_periods` | int | Minimum pre-treatment periods to include |
|
|
1667
|
+
| `min_post_periods` | int | Minimum post-treatment periods to include |
|
|
1668
|
+
|
|
1669
|
+
### SunAbrahamResults
|
|
1670
|
+
|
|
1671
|
+
**Attributes:**
|
|
1672
|
+
|
|
1673
|
+
| Attribute | Description |
|
|
1674
|
+
|-----------|-------------|
|
|
1675
|
+
| `event_study_effects` | Dict mapping relative time to effect info |
|
|
1676
|
+
| `overall_att` | Overall average treatment effect |
|
|
1677
|
+
| `overall_se` | Standard error of overall ATT |
|
|
1678
|
+
| `overall_t_stat` | T-statistic for overall ATT |
|
|
1679
|
+
| `overall_p_value` | P-value for overall ATT |
|
|
1680
|
+
| `overall_conf_int` | Confidence interval for overall ATT |
|
|
1681
|
+
| `cohort_weights` | Dict mapping relative time to cohort weights |
|
|
1682
|
+
| `groups` | List of treatment cohorts |
|
|
1683
|
+
| `time_periods` | List of all time periods |
|
|
1684
|
+
| `n_obs` | Total number of observations |
|
|
1685
|
+
| `n_treated_units` | Number of ever-treated units |
|
|
1686
|
+
| `n_control_units` | Number of never-treated units |
|
|
1687
|
+
| `is_significant` | Boolean for significance at alpha |
|
|
1688
|
+
| `significance_stars` | String of significance stars |
|
|
1689
|
+
| `bootstrap_results` | SABootstrapResults (if bootstrap enabled) |
|
|
1690
|
+
|
|
1691
|
+
**Methods:**
|
|
1692
|
+
|
|
1693
|
+
| Method | Description |
|
|
1694
|
+
|--------|-------------|
|
|
1695
|
+
| `summary(alpha)` | Get formatted summary string |
|
|
1696
|
+
| `print_summary(alpha)` | Print summary to stdout |
|
|
1697
|
+
| `to_dataframe(level)` | Convert to DataFrame ('event_study' or 'cohort') |
|
|
1698
|
+
|
|
1448
1699
|
### HonestDiD
|
|
1449
1700
|
|
|
1450
1701
|
```python
|
|
@@ -1514,6 +1765,81 @@ HonestDiD(
|
|
|
1514
1765
|
| `plot(ax)` | Plot sensitivity analysis |
|
|
1515
1766
|
| `to_dataframe()` | Convert to pandas DataFrame |
|
|
1516
1767
|
|
|
1768
|
+
### PreTrendsPower
|
|
1769
|
+
|
|
1770
|
+
```python
|
|
1771
|
+
PreTrendsPower(
|
|
1772
|
+
alpha=0.05, # Significance level for pre-trends test
|
|
1773
|
+
power=0.80, # Target power for MDV calculation
|
|
1774
|
+
violation_type='linear', # 'linear', 'constant', 'last_period', 'custom'
|
|
1775
|
+
violation_weights=None # Custom weights (required if violation_type='custom')
|
|
1776
|
+
)
|
|
1777
|
+
```
|
|
1778
|
+
|
|
1779
|
+
**fit() Parameters:**
|
|
1780
|
+
|
|
1781
|
+
| Parameter | Type | Description |
|
|
1782
|
+
|-----------|------|-------------|
|
|
1783
|
+
| `results` | MultiPeriodDiDResults | Results from event study |
|
|
1784
|
+
| `M` | float | Specific violation magnitude to evaluate |
|
|
1785
|
+
|
|
1786
|
+
**Methods:**
|
|
1787
|
+
|
|
1788
|
+
| Method | Description |
|
|
1789
|
+
|--------|-------------|
|
|
1790
|
+
| `fit(results, M)` | Compute power analysis for given event study |
|
|
1791
|
+
| `power_at(results, M)` | Compute power for specific violation magnitude |
|
|
1792
|
+
| `power_curve(results, M_grid, n_points)` | Compute power across range of M values |
|
|
1793
|
+
| `sensitivity_to_honest_did(results)` | Compare with HonestDiD analysis |
|
|
1794
|
+
|
|
1795
|
+
### PreTrendsPowerResults
|
|
1796
|
+
|
|
1797
|
+
**Attributes:**
|
|
1798
|
+
|
|
1799
|
+
| Attribute | Description |
|
|
1800
|
+
|-----------|-------------|
|
|
1801
|
+
| `power` | Power to detect the specified violation |
|
|
1802
|
+
| `mdv` | Minimum detectable violation at target power |
|
|
1803
|
+
| `violation_magnitude` | Violation magnitude (M) tested |
|
|
1804
|
+
| `violation_type` | Type of violation pattern |
|
|
1805
|
+
| `alpha` | Significance level |
|
|
1806
|
+
| `target_power` | Target power level |
|
|
1807
|
+
| `n_pre_periods` | Number of pre-treatment periods |
|
|
1808
|
+
| `test_statistic` | Expected test statistic under violation |
|
|
1809
|
+
| `critical_value` | Critical value for pre-trends test |
|
|
1810
|
+
| `noncentrality` | Non-centrality parameter |
|
|
1811
|
+
| `is_informative` | Heuristic check if test is informative |
|
|
1812
|
+
| `power_adequate` | Whether power meets target |
|
|
1813
|
+
|
|
1814
|
+
**Methods:**
|
|
1815
|
+
|
|
1816
|
+
| Method | Description |
|
|
1817
|
+
|--------|-------------|
|
|
1818
|
+
| `summary()` | Get formatted summary string |
|
|
1819
|
+
| `print_summary()` | Print summary to stdout |
|
|
1820
|
+
| `to_dict()` | Convert to dictionary |
|
|
1821
|
+
| `to_dataframe()` | Convert to pandas DataFrame |
|
|
1822
|
+
|
|
1823
|
+
### PreTrendsPowerCurve
|
|
1824
|
+
|
|
1825
|
+
**Attributes:**
|
|
1826
|
+
|
|
1827
|
+
| Attribute | Description |
|
|
1828
|
+
|-----------|-------------|
|
|
1829
|
+
| `M_values` | Array of violation magnitudes |
|
|
1830
|
+
| `powers` | Array of power values |
|
|
1831
|
+
| `mdv` | Minimum detectable violation |
|
|
1832
|
+
| `alpha` | Significance level |
|
|
1833
|
+
| `target_power` | Target power level |
|
|
1834
|
+
| `violation_type` | Type of violation pattern |
|
|
1835
|
+
|
|
1836
|
+
**Methods:**
|
|
1837
|
+
|
|
1838
|
+
| Method | Description |
|
|
1839
|
+
|--------|-------------|
|
|
1840
|
+
| `plot(ax, show_mdv, show_target)` | Plot power curve |
|
|
1841
|
+
| `to_dataframe()` | Convert to DataFrame with M and power columns |
|
|
1842
|
+
|
|
1517
1843
|
### Data Preparation Functions
|
|
1518
1844
|
|
|
1519
1845
|
#### generate_did_data
|