diff-diff 1.0.0__tar.gz → 1.1.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 (34) hide show
  1. {diff_diff-1.0.0 → diff_diff-1.1.0}/PKG-INFO +172 -6
  2. {diff_diff-1.0.0 → diff_diff-1.1.0}/README.md +171 -5
  3. {diff_diff-1.0.0 → diff_diff-1.1.0}/diff_diff/__init__.py +9 -1
  4. {diff_diff-1.0.0 → diff_diff-1.1.0}/diff_diff/diagnostics.py +3 -15
  5. {diff_diff-1.0.0 → diff_diff-1.1.0}/diff_diff/estimators.py +24 -860
  6. {diff_diff-1.0.0 → diff_diff-1.1.0}/diff_diff/honest_did.py +4 -2
  7. {diff_diff-1.0.0 → diff_diff-1.1.0}/diff_diff/power.py +1 -1
  8. {diff_diff-1.0.0 → diff_diff-1.1.0}/diff_diff/staggered.py +50 -19
  9. diff_diff-1.1.0/diff_diff/sun_abraham.py +1198 -0
  10. diff_diff-1.1.0/diff_diff/synthetic_did.py +540 -0
  11. diff_diff-1.1.0/diff_diff/twfe.py +357 -0
  12. {diff_diff-1.0.0 → diff_diff-1.1.0}/diff_diff/visualization.py +4 -1
  13. {diff_diff-1.0.0 → diff_diff-1.1.0}/diff_diff.egg-info/PKG-INFO +172 -6
  14. {diff_diff-1.0.0 → diff_diff-1.1.0}/diff_diff.egg-info/SOURCES.txt +4 -0
  15. {diff_diff-1.0.0 → diff_diff-1.1.0}/pyproject.toml +1 -1
  16. {diff_diff-1.0.0 → diff_diff-1.1.0}/tests/test_staggered.py +9 -3
  17. diff_diff-1.1.0/tests/test_sun_abraham.py +732 -0
  18. {diff_diff-1.0.0 → diff_diff-1.1.0}/diff_diff/bacon.py +0 -0
  19. {diff_diff-1.0.0 → diff_diff-1.1.0}/diff_diff/prep.py +0 -0
  20. {diff_diff-1.0.0 → diff_diff-1.1.0}/diff_diff/results.py +0 -0
  21. {diff_diff-1.0.0 → diff_diff-1.1.0}/diff_diff/utils.py +0 -0
  22. {diff_diff-1.0.0 → diff_diff-1.1.0}/diff_diff.egg-info/dependency_links.txt +0 -0
  23. {diff_diff-1.0.0 → diff_diff-1.1.0}/diff_diff.egg-info/requires.txt +0 -0
  24. {diff_diff-1.0.0 → diff_diff-1.1.0}/diff_diff.egg-info/top_level.txt +0 -0
  25. {diff_diff-1.0.0 → diff_diff-1.1.0}/setup.cfg +0 -0
  26. {diff_diff-1.0.0 → diff_diff-1.1.0}/tests/test_bacon.py +0 -0
  27. {diff_diff-1.0.0 → diff_diff-1.1.0}/tests/test_diagnostics.py +0 -0
  28. {diff_diff-1.0.0 → diff_diff-1.1.0}/tests/test_estimators.py +0 -0
  29. {diff_diff-1.0.0 → diff_diff-1.1.0}/tests/test_honest_did.py +0 -0
  30. {diff_diff-1.0.0 → diff_diff-1.1.0}/tests/test_power.py +0 -0
  31. {diff_diff-1.0.0 → diff_diff-1.1.0}/tests/test_prep.py +0 -0
  32. {diff_diff-1.0.0 → diff_diff-1.1.0}/tests/test_utils.py +0 -0
  33. {diff_diff-1.0.0 → diff_diff-1.1.0}/tests/test_visualization.py +0 -0
  34. {diff_diff-1.0.0 → diff_diff-1.1.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.0.0
3
+ Version: 1.1.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,7 +105,7 @@ 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) estimator for heterogeneous treatment timing
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
@@ -122,7 +122,7 @@ We provide Jupyter notebook tutorials in `docs/tutorials/`:
122
122
  | Notebook | Description |
123
123
  |----------|-------------|
124
124
  | `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 |
125
+ | `02_staggered_did.ipynb` | Staggered adoption with Callaway-Sant'Anna and Sun-Abraham, group-time effects, aggregation methods, Bacon decomposition |
126
126
  | `03_synthetic_did.ipynb` | Synthetic DiD, unit/time weights, inference methods, regularization |
127
127
  | `04_parallel_trends.ipynb` | Testing parallel trends, equivalence tests, placebo tests, diagnostics |
128
128
  | `05_honest_did.ipynb` | Honest DiD sensitivity analysis, bounds, breakdown values, visualization |
@@ -574,7 +574,7 @@ Works with `DifferenceInDifferences` and `TwoWayFixedEffects` estimators.
574
574
  ### Two-Way Fixed Effects (Panel Data)
575
575
 
576
576
  ```python
577
- from diff_diff.estimators import TwoWayFixedEffects
577
+ from diff_diff import TwoWayFixedEffects
578
578
 
579
579
  twfe = TwoWayFixedEffects()
580
580
  results = twfe.fit(
@@ -797,12 +797,115 @@ results = cs.fit(
797
797
  )
798
798
  ```
799
799
 
800
+ ### Sun-Abraham Interaction-Weighted Estimator
801
+
802
+ 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.
803
+
804
+ ```python
805
+ from diff_diff import SunAbraham
806
+
807
+ # Basic usage
808
+ sa = SunAbraham()
809
+ results = sa.fit(
810
+ panel_data,
811
+ outcome='sales',
812
+ unit='firm_id',
813
+ time='year',
814
+ first_treat='first_treat' # 0 for never-treated, else first treatment year
815
+ )
816
+
817
+ # View results
818
+ results.print_summary()
819
+
820
+ # Event study effects (by relative time to treatment)
821
+ for rel_time, effect in results.event_study_effects.items():
822
+ print(f"e={rel_time}: {effect['effect']:.3f} (SE: {effect['se']:.3f})")
823
+
824
+ # Overall ATT
825
+ print(f"Overall ATT: {results.overall_att:.3f} (SE: {results.overall_se:.3f})")
826
+
827
+ # Cohort weights (how each cohort contributes to each event-time estimate)
828
+ for rel_time, weights in results.cohort_weights.items():
829
+ print(f"e={rel_time}: {weights}")
830
+ ```
831
+
832
+ **Parameters:**
833
+
834
+ ```python
835
+ SunAbraham(
836
+ control_group='never_treated', # or 'not_yet_treated'
837
+ anticipation=0, # Periods before treatment with effects
838
+ alpha=0.05, # Significance level
839
+ cluster=None, # Column for cluster SEs
840
+ n_bootstrap=0, # Bootstrap iterations (0 = analytical SEs)
841
+ bootstrap_weights='rademacher', # 'rademacher', 'mammen', or 'webb'
842
+ seed=None # Random seed
843
+ )
844
+ ```
845
+
846
+ **Bootstrap inference:**
847
+
848
+ ```python
849
+ # Bootstrap inference with 999 iterations
850
+ sa = SunAbraham(
851
+ n_bootstrap=999,
852
+ bootstrap_weights='rademacher',
853
+ seed=42
854
+ )
855
+ results = sa.fit(
856
+ data,
857
+ outcome='sales',
858
+ unit='firm_id',
859
+ time='year',
860
+ first_treat='first_treat'
861
+ )
862
+
863
+ # Access bootstrap results
864
+ print(f"Overall ATT: {results.overall_att:.3f}")
865
+ print(f"Bootstrap SE: {results.bootstrap_results.overall_att_se:.3f}")
866
+ print(f"Bootstrap 95% CI: {results.bootstrap_results.overall_att_ci}")
867
+ print(f"Bootstrap p-value: {results.bootstrap_results.overall_att_p_value:.4f}")
868
+ ```
869
+
870
+ **When to use Sun-Abraham vs Callaway-Sant'Anna:**
871
+
872
+ | Aspect | Sun-Abraham | Callaway-Sant'Anna |
873
+ |--------|-------------|-------------------|
874
+ | Approach | Interaction-weighted regression | 2x2 DiD aggregation |
875
+ | Efficiency | More efficient under homogeneous effects | More robust to heterogeneity |
876
+ | Weighting | Weights by cohort share at each relative time | Weights by sample size |
877
+ | Use case | Robustness check, regression-based inference | Primary staggered DiD estimator |
878
+
879
+ **Both estimators should give similar results when:**
880
+ - Treatment effects are relatively homogeneous across cohorts
881
+ - Parallel trends holds
882
+
883
+ **Running both as robustness check:**
884
+
885
+ ```python
886
+ from diff_diff import CallawaySantAnna, SunAbraham
887
+
888
+ # Callaway-Sant'Anna
889
+ cs = CallawaySantAnna()
890
+ cs_results = cs.fit(data, outcome='y', unit='unit', time='time', first_treat='first_treat')
891
+
892
+ # Sun-Abraham
893
+ sa = SunAbraham()
894
+ sa_results = sa.fit(data, outcome='y', unit='unit', time='time', first_treat='first_treat')
895
+
896
+ # Compare
897
+ print(f"Callaway-Sant'Anna ATT: {cs_results.overall_att:.3f}")
898
+ print(f"Sun-Abraham ATT: {sa_results.overall_att:.3f}")
899
+
900
+ # If results differ substantially, investigate heterogeneity
901
+ ```
902
+
800
903
  ### Event Study Visualization
801
904
 
802
905
  Create publication-ready event study plots:
803
906
 
804
907
  ```python
805
- from diff_diff import plot_event_study, MultiPeriodDiD, CallawaySantAnna
908
+ from diff_diff import plot_event_study, MultiPeriodDiD, CallawaySantAnna, SunAbraham
806
909
 
807
910
  # From MultiPeriodDiD
808
911
  did = MultiPeriodDiD()
@@ -814,7 +917,13 @@ plot_event_study(results, title="Treatment Effects Over Time")
814
917
  cs = CallawaySantAnna()
815
918
  results = cs.fit(data, outcome='y', unit='unit', time='period',
816
919
  first_treat='first_treat', aggregate='event_study')
817
- plot_event_study(results, title="Staggered DiD Event Study")
920
+ plot_event_study(results, title="Staggered DiD Event Study (CS)")
921
+
922
+ # From SunAbraham
923
+ sa = SunAbraham()
924
+ results = sa.fit(data, outcome='y', unit='unit', time='period',
925
+ first_treat='first_treat')
926
+ plot_event_study(results, title="Staggered DiD Event Study (SA)")
818
927
 
819
928
  # From a DataFrame
820
929
  df = pd.DataFrame({
@@ -1445,6 +1554,63 @@ SyntheticDiD(
1445
1554
  | `get_unit_weights_df()` | Get unit weights as DataFrame |
1446
1555
  | `get_time_weights_df()` | Get time weights as DataFrame |
1447
1556
 
1557
+ ### SunAbraham
1558
+
1559
+ ```python
1560
+ SunAbraham(
1561
+ control_group='never_treated', # or 'not_yet_treated'
1562
+ anticipation=0, # Periods of anticipation effects
1563
+ alpha=0.05, # Significance level for CIs
1564
+ cluster=None, # Column for cluster-robust SEs
1565
+ n_bootstrap=0, # Bootstrap iterations (0 = analytical SEs)
1566
+ bootstrap_weights='rademacher', # 'rademacher', 'mammen', or 'webb'
1567
+ seed=None # Random seed
1568
+ )
1569
+ ```
1570
+
1571
+ **fit() Parameters:**
1572
+
1573
+ | Parameter | Type | Description |
1574
+ |-----------|------|-------------|
1575
+ | `data` | DataFrame | Panel data |
1576
+ | `outcome` | str | Outcome variable column name |
1577
+ | `unit` | str | Unit identifier column |
1578
+ | `time` | str | Time period column |
1579
+ | `first_treat` | str | Column with first treatment period (0 for never-treated) |
1580
+ | `covariates` | list | Covariate column names |
1581
+ | `min_pre_periods` | int | Minimum pre-treatment periods to include |
1582
+ | `min_post_periods` | int | Minimum post-treatment periods to include |
1583
+
1584
+ ### SunAbrahamResults
1585
+
1586
+ **Attributes:**
1587
+
1588
+ | Attribute | Description |
1589
+ |-----------|-------------|
1590
+ | `event_study_effects` | Dict mapping relative time to effect info |
1591
+ | `overall_att` | Overall average treatment effect |
1592
+ | `overall_se` | Standard error of overall ATT |
1593
+ | `overall_t_stat` | T-statistic for overall ATT |
1594
+ | `overall_p_value` | P-value for overall ATT |
1595
+ | `overall_conf_int` | Confidence interval for overall ATT |
1596
+ | `cohort_weights` | Dict mapping relative time to cohort weights |
1597
+ | `groups` | List of treatment cohorts |
1598
+ | `time_periods` | List of all time periods |
1599
+ | `n_obs` | Total number of observations |
1600
+ | `n_treated_units` | Number of ever-treated units |
1601
+ | `n_control_units` | Number of never-treated units |
1602
+ | `is_significant` | Boolean for significance at alpha |
1603
+ | `significance_stars` | String of significance stars |
1604
+ | `bootstrap_results` | SABootstrapResults (if bootstrap enabled) |
1605
+
1606
+ **Methods:**
1607
+
1608
+ | Method | Description |
1609
+ |--------|-------------|
1610
+ | `summary(alpha)` | Get formatted summary string |
1611
+ | `print_summary(alpha)` | Print summary to stdout |
1612
+ | `to_dataframe(level)` | Convert to DataFrame ('event_study' or 'cohort') |
1613
+
1448
1614
  ### HonestDiD
1449
1615
 
1450
1616
  ```python
@@ -70,7 +70,7 @@ Signif. codes: '***' 0.001, '**' 0.01, '*' 0.05, '.' 0.1
70
70
  - **Wild cluster bootstrap**: Valid inference with few clusters (<50) using Rademacher, Webb, or Mammen weights
71
71
  - **Panel data support**: Two-way fixed effects estimator for panel designs
72
72
  - **Multi-period analysis**: Event-study style DiD with period-specific treatment effects
73
- - **Staggered adoption**: Callaway-Sant'Anna (2021) estimator for heterogeneous treatment timing
73
+ - **Staggered adoption**: Callaway-Sant'Anna (2021) and Sun-Abraham (2021) estimators for heterogeneous treatment timing
74
74
  - **Synthetic DiD**: Combined DiD with synthetic control for improved robustness
75
75
  - **Event study plots**: Publication-ready visualization of treatment effects
76
76
  - **Parallel trends testing**: Multiple methods including equivalence tests
@@ -87,7 +87,7 @@ We provide Jupyter notebook tutorials in `docs/tutorials/`:
87
87
  | Notebook | Description |
88
88
  |----------|-------------|
89
89
  | `01_basic_did.ipynb` | Basic 2x2 DiD, formula interface, covariates, fixed effects, cluster-robust SE, wild bootstrap |
90
- | `02_staggered_did.ipynb` | Staggered adoption with Callaway-Sant'Anna, group-time effects, aggregation methods, Bacon decomposition |
90
+ | `02_staggered_did.ipynb` | Staggered adoption with Callaway-Sant'Anna and Sun-Abraham, group-time effects, aggregation methods, Bacon decomposition |
91
91
  | `03_synthetic_did.ipynb` | Synthetic DiD, unit/time weights, inference methods, regularization |
92
92
  | `04_parallel_trends.ipynb` | Testing parallel trends, equivalence tests, placebo tests, diagnostics |
93
93
  | `05_honest_did.ipynb` | Honest DiD sensitivity analysis, bounds, breakdown values, visualization |
@@ -539,7 +539,7 @@ Works with `DifferenceInDifferences` and `TwoWayFixedEffects` estimators.
539
539
  ### Two-Way Fixed Effects (Panel Data)
540
540
 
541
541
  ```python
542
- from diff_diff.estimators import TwoWayFixedEffects
542
+ from diff_diff import TwoWayFixedEffects
543
543
 
544
544
  twfe = TwoWayFixedEffects()
545
545
  results = twfe.fit(
@@ -762,12 +762,115 @@ results = cs.fit(
762
762
  )
763
763
  ```
764
764
 
765
+ ### Sun-Abraham Interaction-Weighted Estimator
766
+
767
+ 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.
768
+
769
+ ```python
770
+ from diff_diff import SunAbraham
771
+
772
+ # Basic usage
773
+ sa = SunAbraham()
774
+ results = sa.fit(
775
+ panel_data,
776
+ outcome='sales',
777
+ unit='firm_id',
778
+ time='year',
779
+ first_treat='first_treat' # 0 for never-treated, else first treatment year
780
+ )
781
+
782
+ # View results
783
+ results.print_summary()
784
+
785
+ # Event study effects (by relative time to treatment)
786
+ for rel_time, effect in results.event_study_effects.items():
787
+ print(f"e={rel_time}: {effect['effect']:.3f} (SE: {effect['se']:.3f})")
788
+
789
+ # Overall ATT
790
+ print(f"Overall ATT: {results.overall_att:.3f} (SE: {results.overall_se:.3f})")
791
+
792
+ # Cohort weights (how each cohort contributes to each event-time estimate)
793
+ for rel_time, weights in results.cohort_weights.items():
794
+ print(f"e={rel_time}: {weights}")
795
+ ```
796
+
797
+ **Parameters:**
798
+
799
+ ```python
800
+ SunAbraham(
801
+ control_group='never_treated', # or 'not_yet_treated'
802
+ anticipation=0, # Periods before treatment with effects
803
+ alpha=0.05, # Significance level
804
+ cluster=None, # Column for cluster SEs
805
+ n_bootstrap=0, # Bootstrap iterations (0 = analytical SEs)
806
+ bootstrap_weights='rademacher', # 'rademacher', 'mammen', or 'webb'
807
+ seed=None # Random seed
808
+ )
809
+ ```
810
+
811
+ **Bootstrap inference:**
812
+
813
+ ```python
814
+ # Bootstrap inference with 999 iterations
815
+ sa = SunAbraham(
816
+ n_bootstrap=999,
817
+ bootstrap_weights='rademacher',
818
+ seed=42
819
+ )
820
+ results = sa.fit(
821
+ data,
822
+ outcome='sales',
823
+ unit='firm_id',
824
+ time='year',
825
+ first_treat='first_treat'
826
+ )
827
+
828
+ # Access bootstrap results
829
+ print(f"Overall ATT: {results.overall_att:.3f}")
830
+ print(f"Bootstrap SE: {results.bootstrap_results.overall_att_se:.3f}")
831
+ print(f"Bootstrap 95% CI: {results.bootstrap_results.overall_att_ci}")
832
+ print(f"Bootstrap p-value: {results.bootstrap_results.overall_att_p_value:.4f}")
833
+ ```
834
+
835
+ **When to use Sun-Abraham vs Callaway-Sant'Anna:**
836
+
837
+ | Aspect | Sun-Abraham | Callaway-Sant'Anna |
838
+ |--------|-------------|-------------------|
839
+ | Approach | Interaction-weighted regression | 2x2 DiD aggregation |
840
+ | Efficiency | More efficient under homogeneous effects | More robust to heterogeneity |
841
+ | Weighting | Weights by cohort share at each relative time | Weights by sample size |
842
+ | Use case | Robustness check, regression-based inference | Primary staggered DiD estimator |
843
+
844
+ **Both estimators should give similar results when:**
845
+ - Treatment effects are relatively homogeneous across cohorts
846
+ - Parallel trends holds
847
+
848
+ **Running both as robustness check:**
849
+
850
+ ```python
851
+ from diff_diff import CallawaySantAnna, SunAbraham
852
+
853
+ # Callaway-Sant'Anna
854
+ cs = CallawaySantAnna()
855
+ cs_results = cs.fit(data, outcome='y', unit='unit', time='time', first_treat='first_treat')
856
+
857
+ # Sun-Abraham
858
+ sa = SunAbraham()
859
+ sa_results = sa.fit(data, outcome='y', unit='unit', time='time', first_treat='first_treat')
860
+
861
+ # Compare
862
+ print(f"Callaway-Sant'Anna ATT: {cs_results.overall_att:.3f}")
863
+ print(f"Sun-Abraham ATT: {sa_results.overall_att:.3f}")
864
+
865
+ # If results differ substantially, investigate heterogeneity
866
+ ```
867
+
765
868
  ### Event Study Visualization
766
869
 
767
870
  Create publication-ready event study plots:
768
871
 
769
872
  ```python
770
- from diff_diff import plot_event_study, MultiPeriodDiD, CallawaySantAnna
873
+ from diff_diff import plot_event_study, MultiPeriodDiD, CallawaySantAnna, SunAbraham
771
874
 
772
875
  # From MultiPeriodDiD
773
876
  did = MultiPeriodDiD()
@@ -779,7 +882,13 @@ plot_event_study(results, title="Treatment Effects Over Time")
779
882
  cs = CallawaySantAnna()
780
883
  results = cs.fit(data, outcome='y', unit='unit', time='period',
781
884
  first_treat='first_treat', aggregate='event_study')
782
- plot_event_study(results, title="Staggered DiD Event Study")
885
+ plot_event_study(results, title="Staggered DiD Event Study (CS)")
886
+
887
+ # From SunAbraham
888
+ sa = SunAbraham()
889
+ results = sa.fit(data, outcome='y', unit='unit', time='period',
890
+ first_treat='first_treat')
891
+ plot_event_study(results, title="Staggered DiD Event Study (SA)")
783
892
 
784
893
  # From a DataFrame
785
894
  df = pd.DataFrame({
@@ -1410,6 +1519,63 @@ SyntheticDiD(
1410
1519
  | `get_unit_weights_df()` | Get unit weights as DataFrame |
1411
1520
  | `get_time_weights_df()` | Get time weights as DataFrame |
1412
1521
 
1522
+ ### SunAbraham
1523
+
1524
+ ```python
1525
+ SunAbraham(
1526
+ control_group='never_treated', # or 'not_yet_treated'
1527
+ anticipation=0, # Periods of anticipation effects
1528
+ alpha=0.05, # Significance level for CIs
1529
+ cluster=None, # Column for cluster-robust SEs
1530
+ n_bootstrap=0, # Bootstrap iterations (0 = analytical SEs)
1531
+ bootstrap_weights='rademacher', # 'rademacher', 'mammen', or 'webb'
1532
+ seed=None # Random seed
1533
+ )
1534
+ ```
1535
+
1536
+ **fit() Parameters:**
1537
+
1538
+ | Parameter | Type | Description |
1539
+ |-----------|------|-------------|
1540
+ | `data` | DataFrame | Panel data |
1541
+ | `outcome` | str | Outcome variable column name |
1542
+ | `unit` | str | Unit identifier column |
1543
+ | `time` | str | Time period column |
1544
+ | `first_treat` | str | Column with first treatment period (0 for never-treated) |
1545
+ | `covariates` | list | Covariate column names |
1546
+ | `min_pre_periods` | int | Minimum pre-treatment periods to include |
1547
+ | `min_post_periods` | int | Minimum post-treatment periods to include |
1548
+
1549
+ ### SunAbrahamResults
1550
+
1551
+ **Attributes:**
1552
+
1553
+ | Attribute | Description |
1554
+ |-----------|-------------|
1555
+ | `event_study_effects` | Dict mapping relative time to effect info |
1556
+ | `overall_att` | Overall average treatment effect |
1557
+ | `overall_se` | Standard error of overall ATT |
1558
+ | `overall_t_stat` | T-statistic for overall ATT |
1559
+ | `overall_p_value` | P-value for overall ATT |
1560
+ | `overall_conf_int` | Confidence interval for overall ATT |
1561
+ | `cohort_weights` | Dict mapping relative time to cohort weights |
1562
+ | `groups` | List of treatment cohorts |
1563
+ | `time_periods` | List of all time periods |
1564
+ | `n_obs` | Total number of observations |
1565
+ | `n_treated_units` | Number of ever-treated units |
1566
+ | `n_control_units` | Number of never-treated units |
1567
+ | `is_significant` | Boolean for significance at alpha |
1568
+ | `significance_stars` | String of significance stars |
1569
+ | `bootstrap_results` | SABootstrapResults (if bootstrap enabled) |
1570
+
1571
+ **Methods:**
1572
+
1573
+ | Method | Description |
1574
+ |--------|-------------|
1575
+ | `summary(alpha)` | Get formatted summary string |
1576
+ | `print_summary(alpha)` | Print summary to stdout |
1577
+ | `to_dataframe(level)` | Convert to DataFrame ('event_study' or 'cohort') |
1578
+
1413
1579
  ### HonestDiD
1414
1580
 
1415
1581
  ```python
@@ -69,6 +69,11 @@ from diff_diff.staggered import (
69
69
  CSBootstrapResults,
70
70
  GroupTimeEffect,
71
71
  )
72
+ from diff_diff.sun_abraham import (
73
+ SABootstrapResults,
74
+ SunAbraham,
75
+ SunAbrahamResults,
76
+ )
72
77
  from diff_diff.utils import (
73
78
  WildBootstrapResults,
74
79
  check_parallel_trends,
@@ -85,7 +90,7 @@ from diff_diff.visualization import (
85
90
  plot_sensitivity,
86
91
  )
87
92
 
88
- __version__ = "1.0.0"
93
+ __version__ = "1.1.0"
89
94
  __all__ = [
90
95
  # Estimators
91
96
  "DifferenceInDifferences",
@@ -93,6 +98,7 @@ __all__ = [
93
98
  "MultiPeriodDiD",
94
99
  "SyntheticDiD",
95
100
  "CallawaySantAnna",
101
+ "SunAbraham",
96
102
  # Bacon Decomposition
97
103
  "BaconDecomposition",
98
104
  "BaconDecompositionResults",
@@ -107,6 +113,8 @@ __all__ = [
107
113
  "CallawaySantAnnaResults",
108
114
  "CSBootstrapResults",
109
115
  "GroupTimeEffect",
116
+ "SunAbrahamResults",
117
+ "SABootstrapResults",
110
118
  # Visualization
111
119
  "plot_event_study",
112
120
  "plot_group_effects",
@@ -18,22 +18,10 @@ import numpy as np
18
18
  import pandas as pd
19
19
 
20
20
  from diff_diff.estimators import DifferenceInDifferences
21
+ from diff_diff.results import _get_significance_stars
21
22
  from diff_diff.utils import compute_confidence_interval, compute_p_value
22
23
 
23
24
 
24
- def _get_significance_stars(p_value: float) -> str:
25
- """Return significance stars based on p-value."""
26
- if p_value < 0.001:
27
- return "***"
28
- elif p_value < 0.01:
29
- return "**"
30
- elif p_value < 0.05:
31
- return "*"
32
- elif p_value < 0.1:
33
- return "."
34
- return ""
35
-
36
-
37
25
  @dataclass
38
26
  class PlaceboTestResults:
39
27
  """
@@ -633,7 +621,7 @@ def permutation_test(
633
621
  time=time
634
622
  )
635
623
  permuted_effects[i] = perm_results.att
636
- except Exception:
624
+ except (ValueError, KeyError, np.linalg.LinAlgError):
637
625
  # Handle edge cases where fitting fails
638
626
  permuted_effects[i] = np.nan
639
627
 
@@ -744,7 +732,7 @@ def leave_one_out_test(
744
732
  time=time
745
733
  )
746
734
  loo_effects[u] = loo_results.att
747
- except Exception:
735
+ except (ValueError, KeyError, np.linalg.LinAlgError):
748
736
  # Skip units that cause fitting issues
749
737
  loo_effects[u] = np.nan
750
738