diff-diff 0.5.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.
Files changed (27) hide show
  1. {diff_diff-0.5.0 → diff_diff-0.6.0}/PKG-INFO +194 -6
  2. {diff_diff-0.5.0 → diff_diff-0.6.0}/README.md +192 -4
  3. {diff_diff-0.5.0 → diff_diff-0.6.0}/diff_diff/__init__.py +53 -30
  4. {diff_diff-0.5.0 → diff_diff-0.6.0}/diff_diff/diagnostics.py +3 -0
  5. {diff_diff-0.5.0 → diff_diff-0.6.0}/diff_diff/estimators.py +6 -4
  6. diff_diff-0.6.0/diff_diff/honest_did.py +1491 -0
  7. {diff_diff-0.5.0 → diff_diff-0.6.0}/diff_diff/prep.py +1 -1
  8. {diff_diff-0.5.0 → diff_diff-0.6.0}/diff_diff/staggered.py +341 -52
  9. {diff_diff-0.5.0 → diff_diff-0.6.0}/diff_diff/utils.py +21 -21
  10. {diff_diff-0.5.0 → diff_diff-0.6.0}/diff_diff/visualization.py +350 -1
  11. {diff_diff-0.5.0 → diff_diff-0.6.0}/diff_diff.egg-info/PKG-INFO +194 -6
  12. {diff_diff-0.5.0 → diff_diff-0.6.0}/diff_diff.egg-info/SOURCES.txt +3 -0
  13. {diff_diff-0.5.0 → diff_diff-0.6.0}/pyproject.toml +11 -3
  14. {diff_diff-0.5.0 → diff_diff-0.6.0}/tests/test_diagnostics.py +5 -8
  15. diff_diff-0.6.0/tests/test_honest_did.py +699 -0
  16. diff_diff-0.6.0/tests/test_staggered.py +752 -0
  17. diff_diff-0.6.0/tests/test_utils.py +1270 -0
  18. {diff_diff-0.5.0 → diff_diff-0.6.0}/tests/test_wild_bootstrap.py +3 -4
  19. diff_diff-0.5.0/tests/test_staggered.py +0 -390
  20. {diff_diff-0.5.0 → diff_diff-0.6.0}/diff_diff/results.py +0 -0
  21. {diff_diff-0.5.0 → diff_diff-0.6.0}/diff_diff.egg-info/dependency_links.txt +0 -0
  22. {diff_diff-0.5.0 → diff_diff-0.6.0}/diff_diff.egg-info/requires.txt +0 -0
  23. {diff_diff-0.5.0 → diff_diff-0.6.0}/diff_diff.egg-info/top_level.txt +0 -0
  24. {diff_diff-0.5.0 → diff_diff-0.6.0}/setup.cfg +0 -0
  25. {diff_diff-0.5.0 → diff_diff-0.6.0}/tests/test_estimators.py +1 -1
  26. {diff_diff-0.5.0 → diff_diff-0.6.0}/tests/test_prep.py +7 -7
  27. {diff_diff-0.5.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.5.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://github.com/igerber/diff-diff#readme
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
@@ -110,8 +110,21 @@ Signif. codes: '***' 0.001, '**' 0.01, '*' 0.05, '.' 0.1
110
110
  - **Event study plots**: Publication-ready visualization of treatment effects
111
111
  - **Parallel trends testing**: Multiple methods including equivalence tests
112
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
113
114
  - **Data prep utilities**: Helper functions for common data preparation tasks
114
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
+
115
128
  ## Data Preparation
116
129
 
117
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.
@@ -720,14 +733,31 @@ CallawaySantAnna(
720
733
  estimation_method='dr', # 'dr', 'ipw', or 'reg'
721
734
  alpha=0.05, # Significance level
722
735
  cluster=None, # Column for cluster SEs
723
- n_bootstrap=0, # Must be 0 (bootstrap not yet implemented)
736
+ n_bootstrap=0, # Bootstrap iterations (0 = analytical SEs)
724
737
  seed=None # Random seed
725
738
  )
726
739
  ```
727
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
+
728
759
  **Current limitations:**
729
- - Bootstrap inference (`n_bootstrap > 0`) is not yet implemented
730
- - Covariate adjustment for conditional parallel trends is not yet implemented
760
+ - Bootstrap inference (`n_bootstrap > 0`) is not yet fully implemented
731
761
 
732
762
  ### Event Study Visualization
733
763
 
@@ -1004,6 +1034,81 @@ print(f"TOST p-value: {results['tost_p_value']:.4f}")
1004
1034
  print(f"Trends equivalent: {results['equivalent']}")
1005
1035
  ```
1006
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
+
1007
1112
  ### Placebo Tests
1008
1113
 
1009
1114
  Placebo tests help validate the parallel trends assumption by checking whether effects appear where they shouldn't (before treatment or in untreated groups).
@@ -1302,6 +1407,75 @@ SyntheticDiD(
1302
1407
  | `get_unit_weights_df()` | Get unit weights as DataFrame |
1303
1408
  | `get_time_weights_df()` | Get time weights as DataFrame |
1304
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
+
1305
1479
  ### Data Preparation Functions
1306
1480
 
1307
1481
  #### generate_did_data
@@ -1525,9 +1699,23 @@ This library implements methods from the following scholarly works:
1525
1699
 
1526
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)
1527
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
+
1528
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)
1529
1709
 
1530
- - **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)
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.
1531
1719
 
1532
1720
  ### Multi-Period and Staggered Adoption
1533
1721
 
@@ -75,8 +75,21 @@ Signif. codes: '***' 0.001, '**' 0.01, '*' 0.05, '.' 0.1
75
75
  - **Event study plots**: Publication-ready visualization of treatment effects
76
76
  - **Parallel trends testing**: Multiple methods including equivalence tests
77
77
  - **Placebo tests**: Comprehensive diagnostics including fake timing, fake group, permutation, and leave-one-out tests
78
+ - **Honest DiD sensitivity analysis**: Rambachan-Roth (2023) bounds and breakdown analysis for parallel trends violations
78
79
  - **Data prep utilities**: Helper functions for common data preparation tasks
79
80
 
81
+ ## Tutorials
82
+
83
+ We provide Jupyter notebook tutorials in `docs/tutorials/`:
84
+
85
+ | Notebook | Description |
86
+ |----------|-------------|
87
+ | `01_basic_did.ipynb` | Basic 2x2 DiD, formula interface, covariates, fixed effects, cluster-robust SE, wild bootstrap |
88
+ | `02_staggered_did.ipynb` | Staggered adoption with Callaway-Sant'Anna, group-time effects, aggregation methods |
89
+ | `03_synthetic_did.ipynb` | Synthetic DiD, unit/time weights, inference methods, regularization |
90
+ | `04_parallel_trends.ipynb` | Testing parallel trends, equivalence tests, placebo tests, diagnostics |
91
+ | `05_honest_did.ipynb` | Honest DiD sensitivity analysis, bounds, breakdown values, visualization |
92
+
80
93
  ## Data Preparation
81
94
 
82
95
  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.
@@ -685,14 +698,31 @@ CallawaySantAnna(
685
698
  estimation_method='dr', # 'dr', 'ipw', or 'reg'
686
699
  alpha=0.05, # Significance level
687
700
  cluster=None, # Column for cluster SEs
688
- n_bootstrap=0, # Must be 0 (bootstrap not yet implemented)
701
+ n_bootstrap=0, # Bootstrap iterations (0 = analytical SEs)
689
702
  seed=None # Random seed
690
703
  )
691
704
  ```
692
705
 
706
+ **Covariate adjustment for conditional parallel trends:**
707
+
708
+ When parallel trends only holds conditional on covariates, use the `covariates` parameter:
709
+
710
+ ```python
711
+ # Doubly robust estimation with covariates
712
+ cs = CallawaySantAnna(estimation_method='dr') # 'dr', 'ipw', or 'reg'
713
+ results = cs.fit(
714
+ data,
715
+ outcome='sales',
716
+ unit='firm_id',
717
+ time='year',
718
+ first_treat='first_treat',
719
+ covariates=['size', 'age', 'industry'], # Covariates for conditional PT
720
+ aggregate='event_study'
721
+ )
722
+ ```
723
+
693
724
  **Current limitations:**
694
- - Bootstrap inference (`n_bootstrap > 0`) is not yet implemented
695
- - Covariate adjustment for conditional parallel trends is not yet implemented
725
+ - Bootstrap inference (`n_bootstrap > 0`) is not yet fully implemented
696
726
 
697
727
  ### Event Study Visualization
698
728
 
@@ -969,6 +999,81 @@ print(f"TOST p-value: {results['tost_p_value']:.4f}")
969
999
  print(f"Trends equivalent: {results['equivalent']}")
970
1000
  ```
971
1001
 
1002
+ ### Honest DiD Sensitivity Analysis (Rambachan-Roth)
1003
+
1004
+ 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.
1005
+
1006
+ ```python
1007
+ from diff_diff import HonestDiD, MultiPeriodDiD
1008
+
1009
+ # First, fit a standard event study
1010
+ did = MultiPeriodDiD()
1011
+ event_results = did.fit(
1012
+ data,
1013
+ outcome='outcome',
1014
+ treatment='treated',
1015
+ time='period',
1016
+ post_periods=[5, 6, 7, 8, 9]
1017
+ )
1018
+
1019
+ # Compute honest bounds with relative magnitudes restriction
1020
+ # M=1 means post-treatment violations can be up to 1x the worst pre-treatment violation
1021
+ honest = HonestDiD(method='relative_magnitude', M=1.0)
1022
+ honest_results = honest.fit(event_results)
1023
+
1024
+ print(honest_results.summary())
1025
+ print(f"Original estimate: {honest_results.original_estimate:.4f}")
1026
+ print(f"Robust 95% CI: [{honest_results.ci_lb:.4f}, {honest_results.ci_ub:.4f}]")
1027
+ print(f"Effect robust to violations: {honest_results.is_significant}")
1028
+ ```
1029
+
1030
+ **Sensitivity analysis over M values:**
1031
+
1032
+ ```python
1033
+ # How do results change as we allow larger violations?
1034
+ sensitivity = honest.sensitivity_analysis(
1035
+ event_results,
1036
+ M_grid=[0, 0.5, 1.0, 1.5, 2.0]
1037
+ )
1038
+
1039
+ print(sensitivity.summary())
1040
+ print(f"Breakdown value: M = {sensitivity.breakdown_M}")
1041
+ # Breakdown = smallest M where the robust CI includes zero
1042
+ ```
1043
+
1044
+ **Breakdown value:**
1045
+
1046
+ The breakdown value tells you how robust your conclusion is:
1047
+
1048
+ ```python
1049
+ breakdown = honest.breakdown_value(event_results)
1050
+ if breakdown >= 1.0:
1051
+ print("Result holds even if post-treatment violations are as bad as pre-treatment")
1052
+ else:
1053
+ print(f"Result requires violations smaller than {breakdown:.1f}x pre-treatment")
1054
+ ```
1055
+
1056
+ **Smoothness restriction (alternative approach):**
1057
+
1058
+ ```python
1059
+ # Bounds second differences of trend violations
1060
+ # M=0 means linear extrapolation of pre-trends
1061
+ honest_smooth = HonestDiD(method='smoothness', M=0.5)
1062
+ smooth_results = honest_smooth.fit(event_results)
1063
+ ```
1064
+
1065
+ **Visualization:**
1066
+
1067
+ ```python
1068
+ from diff_diff import plot_sensitivity, plot_honest_event_study
1069
+
1070
+ # Plot sensitivity analysis
1071
+ plot_sensitivity(sensitivity, title="Sensitivity to Parallel Trends Violations")
1072
+
1073
+ # Event study with honest confidence intervals
1074
+ plot_honest_event_study(event_results, honest_results)
1075
+ ```
1076
+
972
1077
  ### Placebo Tests
973
1078
 
974
1079
  Placebo tests help validate the parallel trends assumption by checking whether effects appear where they shouldn't (before treatment or in untreated groups).
@@ -1267,6 +1372,75 @@ SyntheticDiD(
1267
1372
  | `get_unit_weights_df()` | Get unit weights as DataFrame |
1268
1373
  | `get_time_weights_df()` | Get time weights as DataFrame |
1269
1374
 
1375
+ ### HonestDiD
1376
+
1377
+ ```python
1378
+ HonestDiD(
1379
+ method='relative_magnitude', # 'relative_magnitude' or 'smoothness'
1380
+ M=None, # Restriction parameter (default: 1.0 for RM, 0.0 for SD)
1381
+ alpha=0.05, # Significance level for CIs
1382
+ l_vec=None # Linear combination vector for target parameter
1383
+ )
1384
+ ```
1385
+
1386
+ **fit() Parameters:**
1387
+
1388
+ | Parameter | Type | Description |
1389
+ |-----------|------|-------------|
1390
+ | `results` | MultiPeriodDiDResults | Results from MultiPeriodDiD.fit() |
1391
+ | `M` | float | Restriction parameter (overrides constructor value) |
1392
+
1393
+ **Methods:**
1394
+
1395
+ | Method | Description |
1396
+ |--------|-------------|
1397
+ | `fit(results, M)` | Compute bounds for given event study results |
1398
+ | `sensitivity_analysis(results, M_grid)` | Compute bounds over grid of M values |
1399
+ | `breakdown_value(results, tol)` | Find smallest M where CI includes zero |
1400
+
1401
+ ### HonestDiDResults
1402
+
1403
+ **Attributes:**
1404
+
1405
+ | Attribute | Description |
1406
+ |-----------|-------------|
1407
+ | `original_estimate` | Point estimate under parallel trends |
1408
+ | `lb` | Lower bound of identified set |
1409
+ | `ub` | Upper bound of identified set |
1410
+ | `ci_lb` | Lower bound of robust confidence interval |
1411
+ | `ci_ub` | Upper bound of robust confidence interval |
1412
+ | `ci_width` | Width of robust CI |
1413
+ | `M` | Restriction parameter used |
1414
+ | `method` | Restriction method ('relative_magnitude' or 'smoothness') |
1415
+ | `alpha` | Significance level |
1416
+ | `is_significant` | True if robust CI excludes zero |
1417
+
1418
+ **Methods:**
1419
+
1420
+ | Method | Description |
1421
+ |--------|-------------|
1422
+ | `summary()` | Get formatted summary string |
1423
+ | `to_dict()` | Convert to dictionary |
1424
+ | `to_dataframe()` | Convert to pandas DataFrame |
1425
+
1426
+ ### SensitivityResults
1427
+
1428
+ **Attributes:**
1429
+
1430
+ | Attribute | Description |
1431
+ |-----------|-------------|
1432
+ | `M_grid` | Array of M values analyzed |
1433
+ | `results` | List of HonestDiDResults for each M |
1434
+ | `breakdown_M` | Smallest M where CI includes zero (None if always significant) |
1435
+
1436
+ **Methods:**
1437
+
1438
+ | Method | Description |
1439
+ |--------|-------------|
1440
+ | `summary()` | Get formatted summary string |
1441
+ | `plot(ax)` | Plot sensitivity analysis |
1442
+ | `to_dataframe()` | Convert to pandas DataFrame |
1443
+
1270
1444
  ### Data Preparation Functions
1271
1445
 
1272
1446
  #### generate_did_data
@@ -1490,9 +1664,23 @@ This library implements methods from the following scholarly works:
1490
1664
 
1491
1665
  - **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)
1492
1666
 
1667
+ - **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)
1668
+
1669
+ ### Honest DiD / Sensitivity Analysis
1670
+
1671
+ The `HonestDiD` module implements sensitivity analysis methods for relaxing the parallel trends assumption:
1672
+
1493
1673
  - **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)
1494
1674
 
1495
- - **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)
1675
+ This paper introduces the "Honest DiD" framework implemented in our `HonestDiD` class:
1676
+ - **Relative Magnitudes (ΔRM)**: Bounds post-treatment violations by a multiple of observed pre-treatment violations
1677
+ - **Smoothness (ΔSD)**: Bounds on second differences of trend violations, allowing for linear extrapolation of pre-trends
1678
+ - **Breakdown Analysis**: Finding the smallest violation magnitude that would overturn conclusions
1679
+ - **Robust Confidence Intervals**: Valid inference under partial identification
1680
+
1681
+ - **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)
1682
+
1683
+ Discusses functional form sensitivity in parallel trends assumptions, relevant to understanding when smoothness restrictions are appropriate.
1496
1684
 
1497
1685
  ### Multi-Period and Staggered Adoption
1498
1686
 
@@ -5,16 +5,42 @@ This library provides sklearn-like estimators for causal inference
5
5
  using the difference-in-differences methodology.
6
6
  """
7
7
 
8
+ from diff_diff.diagnostics import (
9
+ PlaceboTestResults,
10
+ leave_one_out_test,
11
+ permutation_test,
12
+ placebo_group_test,
13
+ placebo_timing_test,
14
+ run_all_placebo_tests,
15
+ run_placebo_test,
16
+ )
8
17
  from diff_diff.estimators import (
9
18
  DifferenceInDifferences,
10
- TwoWayFixedEffects,
11
19
  MultiPeriodDiD,
12
20
  SyntheticDiD,
21
+ TwoWayFixedEffects,
13
22
  )
14
- from diff_diff.staggered import (
15
- CallawaySantAnna,
16
- CallawaySantAnnaResults,
17
- GroupTimeEffect,
23
+ from diff_diff.honest_did import (
24
+ DeltaRM,
25
+ DeltaSD,
26
+ DeltaSDRM,
27
+ HonestDiD,
28
+ HonestDiDResults,
29
+ SensitivityResults,
30
+ compute_honest_did,
31
+ sensitivity_plot,
32
+ )
33
+ from diff_diff.prep import (
34
+ aggregate_to_cohorts,
35
+ balance_panel,
36
+ create_event_time,
37
+ generate_did_data,
38
+ make_post_indicator,
39
+ make_treatment_indicator,
40
+ rank_control_units,
41
+ summarize_did_data,
42
+ validate_did_data,
43
+ wide_to_long,
18
44
  )
19
45
  from diff_diff.results import (
20
46
  DiDResults,
@@ -22,40 +48,26 @@ from diff_diff.results import (
22
48
  PeriodEffect,
23
49
  SyntheticDiDResults,
24
50
  )
25
- from diff_diff.visualization import (
26
- plot_event_study,
27
- plot_group_effects,
28
- )
29
- from diff_diff.prep import (
30
- make_treatment_indicator,
31
- make_post_indicator,
32
- wide_to_long,
33
- balance_panel,
34
- validate_did_data,
35
- summarize_did_data,
36
- generate_did_data,
37
- create_event_time,
38
- aggregate_to_cohorts,
39
- rank_control_units,
51
+ from diff_diff.staggered import (
52
+ CallawaySantAnna,
53
+ CallawaySantAnnaResults,
54
+ GroupTimeEffect,
40
55
  )
41
56
  from diff_diff.utils import (
57
+ WildBootstrapResults,
42
58
  check_parallel_trends,
43
59
  check_parallel_trends_robust,
44
60
  equivalence_test_trends,
45
- WildBootstrapResults,
46
61
  wild_bootstrap_se,
47
62
  )
48
- from diff_diff.diagnostics import (
49
- PlaceboTestResults,
50
- run_placebo_test,
51
- placebo_timing_test,
52
- placebo_group_test,
53
- permutation_test,
54
- leave_one_out_test,
55
- run_all_placebo_tests,
63
+ from diff_diff.visualization import (
64
+ plot_event_study,
65
+ plot_group_effects,
66
+ plot_honest_event_study,
67
+ plot_sensitivity,
56
68
  )
57
69
 
58
- __version__ = "0.5.0"
70
+ __version__ = "0.6.0"
59
71
  __all__ = [
60
72
  # Estimators
61
73
  "DifferenceInDifferences",
@@ -73,6 +85,8 @@ __all__ = [
73
85
  # Visualization
74
86
  "plot_event_study",
75
87
  "plot_group_effects",
88
+ "plot_sensitivity",
89
+ "plot_honest_event_study",
76
90
  # Parallel trends testing
77
91
  "check_parallel_trends",
78
92
  "check_parallel_trends_robust",
@@ -99,4 +113,13 @@ __all__ = [
99
113
  "create_event_time",
100
114
  "aggregate_to_cohorts",
101
115
  "rank_control_units",
116
+ # Honest DiD sensitivity analysis
117
+ "HonestDiD",
118
+ "HonestDiDResults",
119
+ "SensitivityResults",
120
+ "DeltaSD",
121
+ "DeltaRM",
122
+ "DeltaSDRM",
123
+ "compute_honest_did",
124
+ "sensitivity_plot",
102
125
  ]
@@ -360,6 +360,9 @@ def run_placebo_test(
360
360
  **estimator_kwargs
361
361
  )
362
362
 
363
+ # This should never be reached due to validation above
364
+ raise ValueError(f"Unknown test type: {test_type}")
365
+
363
366
 
364
367
  def placebo_timing_test(
365
368
  data: pd.DataFrame,
@@ -603,6 +603,7 @@ class DifferenceInDifferences:
603
603
  """
604
604
  if not self.is_fitted_:
605
605
  raise RuntimeError("Model must be fitted before calling summary()")
606
+ assert self.results_ is not None
606
607
  return self.results_.summary()
607
608
 
608
609
  def print_summary(self) -> None:
@@ -641,7 +642,7 @@ class TwoWayFixedEffects(DifferenceInDifferences):
641
642
  staggered designs.
642
643
  """
643
644
 
644
- def fit(
645
+ def fit( # type: ignore[override]
645
646
  self,
646
647
  data: pd.DataFrame,
647
648
  outcome: str,
@@ -763,7 +764,7 @@ class TwoWayFixedEffects(DifferenceInDifferences):
763
764
  n_treated=n_treated,
764
765
  n_control=n_control,
765
766
  alpha=self.alpha,
766
- coefficients={"ATT": att},
767
+ coefficients={"ATT": float(att)},
767
768
  vcov=vcov,
768
769
  residuals=residuals,
769
770
  fitted_values=fitted,
@@ -892,7 +893,7 @@ class MultiPeriodDiD(DifferenceInDifferences):
892
893
  The average ATT is computed as the mean of the δ_t coefficients.
893
894
  """
894
895
 
895
- def fit(
896
+ def fit( # type: ignore[override]
896
897
  self,
897
898
  data: pd.DataFrame,
898
899
  outcome: str,
@@ -1166,6 +1167,7 @@ class MultiPeriodDiD(DifferenceInDifferences):
1166
1167
  """
1167
1168
  if not self.is_fitted_:
1168
1169
  raise RuntimeError("Model must be fitted before calling summary()")
1170
+ assert self.results_ is not None
1169
1171
  return self.results_.summary()
1170
1172
 
1171
1173
 
@@ -1284,7 +1286,7 @@ class SyntheticDiD(DifferenceInDifferences):
1284
1286
  self._unit_weights = None
1285
1287
  self._time_weights = None
1286
1288
 
1287
- def fit(
1289
+ def fit( # type: ignore[override]
1288
1290
  self,
1289
1291
  data: pd.DataFrame,
1290
1292
  outcome: str,