diff-diff 0.3.0__tar.gz → 0.4.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diff-diff
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: A library for Difference-in-Differences causal inference analysis
5
5
  Author: diff-diff contributors
6
6
  License-Expression: MIT
@@ -104,7 +104,10 @@ Signif. codes: '***' 0.001, '**' 0.01, '*' 0.05, '.' 0.1
104
104
  - **Robust inference**: Heteroskedasticity-robust (HC1) and cluster-robust standard errors
105
105
  - **Panel data support**: Two-way fixed effects estimator for panel designs
106
106
  - **Multi-period analysis**: Event-study style DiD with period-specific treatment effects
107
+ - **Staggered adoption**: Callaway-Sant'Anna (2021) estimator for heterogeneous treatment timing
107
108
  - **Synthetic DiD**: Combined DiD with synthetic control for improved robustness
109
+ - **Event study plots**: Publication-ready visualization of treatment effects
110
+ - **Parallel trends testing**: Multiple methods including equivalence tests
108
111
  - **Data prep utilities**: Helper functions for common data preparation tasks
109
112
 
110
113
  ## Data Preparation
@@ -342,6 +345,79 @@ cohort_data = aggregate_to_cohorts(
342
345
  # Result: mean outcome by treatment group and period
343
346
  ```
344
347
 
348
+ ### Rank Control Units
349
+
350
+ Select the best control units for DiD or Synthetic DiD analysis by ranking them based on pre-treatment outcome similarity:
351
+
352
+ ```python
353
+ from diff_diff import rank_control_units, generate_did_data
354
+
355
+ # Generate sample data
356
+ data = generate_did_data(n_units=50, n_periods=6, seed=42)
357
+
358
+ # Rank control units by their similarity to treated units
359
+ ranking = rank_control_units(
360
+ data,
361
+ unit_column='unit',
362
+ time_column='period',
363
+ outcome_column='outcome',
364
+ treatment_column='treated',
365
+ n_top=10 # Return top 10 controls
366
+ )
367
+
368
+ print(ranking[['unit', 'quality_score', 'pre_trend_rmse']])
369
+ ```
370
+
371
+ Output:
372
+ ```
373
+ unit quality_score pre_trend_rmse
374
+ 0 35 1.0000 0.4521
375
+ 1 42 0.9234 0.5123
376
+ 2 28 0.8876 0.5892
377
+ ...
378
+ ```
379
+
380
+ With covariates for matching:
381
+
382
+ ```python
383
+ # Add covariate-based matching
384
+ ranking = rank_control_units(
385
+ data,
386
+ unit_column='unit',
387
+ time_column='period',
388
+ outcome_column='outcome',
389
+ treatment_column='treated',
390
+ covariates=['size', 'age'], # Match on these too
391
+ outcome_weight=0.7, # 70% weight on outcome trends
392
+ covariate_weight=0.3 # 30% weight on covariate similarity
393
+ )
394
+ ```
395
+
396
+ Filter data for SyntheticDiD using top controls:
397
+
398
+ ```python
399
+ from diff_diff import SyntheticDiD
400
+
401
+ # Get top control units
402
+ top_controls = ranking['unit'].tolist()
403
+
404
+ # Filter data to treated + top controls
405
+ filtered_data = data[
406
+ (data['treated'] == 1) | (data['unit'].isin(top_controls))
407
+ ]
408
+
409
+ # Fit SyntheticDiD with selected controls
410
+ sdid = SyntheticDiD()
411
+ results = sdid.fit(
412
+ filtered_data,
413
+ outcome='outcome',
414
+ treatment='treated',
415
+ unit='unit',
416
+ time='period',
417
+ post_periods=[3, 4, 5]
418
+ )
419
+ ```
420
+
345
421
  ## Usage
346
422
 
347
423
  ### Basic DiD with Column Names
@@ -522,6 +598,148 @@ Signif. codes: '***' 0.001, '**' 0.01, '*' 0.05, '.' 0.1
522
598
  ================================================================================
523
599
  ```
524
600
 
601
+ ### Staggered Difference-in-Differences (Callaway-Sant'Anna)
602
+
603
+ When treatment is adopted at different times by different units, traditional TWFE estimators can be biased. The Callaway-Sant'Anna estimator provides unbiased estimates with staggered adoption.
604
+
605
+ ```python
606
+ from diff_diff import CallawaySantAnna
607
+
608
+ # Panel data with staggered treatment
609
+ # 'first_treat' = period when unit was first treated (0 if never treated)
610
+ cs = CallawaySantAnna()
611
+ results = cs.fit(
612
+ panel_data,
613
+ outcome='sales',
614
+ unit='firm_id',
615
+ time='year',
616
+ first_treat='first_treat', # 0 for never-treated, else first treatment year
617
+ aggregate='event_study' # Compute event study effects
618
+ )
619
+
620
+ # View results
621
+ results.print_summary()
622
+
623
+ # Access group-time effects ATT(g,t)
624
+ for (group, time), effect in results.group_time_effects.items():
625
+ print(f"Cohort {group}, Period {time}: {effect['effect']:.3f}")
626
+
627
+ # Event study effects (averaged by relative time)
628
+ for rel_time, effect in results.event_study_effects.items():
629
+ print(f"e={rel_time}: {effect['effect']:.3f} (SE: {effect['se']:.3f})")
630
+
631
+ # Convert to DataFrame
632
+ df = results.to_dataframe(level='event_study')
633
+ ```
634
+
635
+ Output:
636
+ ```
637
+ =====================================================================================
638
+ Callaway-Sant'Anna Staggered Difference-in-Differences Results
639
+ =====================================================================================
640
+
641
+ Total observations: 600
642
+ Treated units: 35
643
+ Control units: 15
644
+ Treatment cohorts: 3
645
+ Time periods: 8
646
+ Control group: never_treated
647
+
648
+ -------------------------------------------------------------------------------------
649
+ Overall Average Treatment Effect on the Treated
650
+ -------------------------------------------------------------------------------------
651
+ Parameter Estimate Std. Err. t-stat P>|t| Sig.
652
+ -------------------------------------------------------------------------------------
653
+ ATT 2.5000 0.3521 7.101 0.0000 ***
654
+ -------------------------------------------------------------------------------------
655
+
656
+ 95% Confidence Interval: [1.8099, 3.1901]
657
+
658
+ -------------------------------------------------------------------------------------
659
+ Event Study (Dynamic) Effects
660
+ -------------------------------------------------------------------------------------
661
+ Rel. Period Estimate Std. Err. t-stat P>|t| Sig.
662
+ -------------------------------------------------------------------------------------
663
+ 0 2.1000 0.4521 4.645 0.0000 ***
664
+ 1 2.5000 0.4123 6.064 0.0000 ***
665
+ 2 2.8000 0.5234 5.349 0.0000 ***
666
+ -------------------------------------------------------------------------------------
667
+
668
+ Signif. codes: '***' 0.001, '**' 0.01, '*' 0.05, '.' 0.1
669
+ =====================================================================================
670
+ ```
671
+
672
+ **When to use Callaway-Sant'Anna vs TWFE:**
673
+
674
+ | Scenario | Use TWFE | Use Callaway-Sant'Anna |
675
+ |----------|----------|------------------------|
676
+ | All units treated at same time | ✓ | ✓ |
677
+ | Staggered adoption, homogeneous effects | ✓ | ✓ |
678
+ | Staggered adoption, heterogeneous effects | ✗ | ✓ |
679
+ | Need event study with staggered timing | ✗ | ✓ |
680
+ | Fewer than ~20 treated units | ✓ | Depends on design |
681
+
682
+ **Parameters:**
683
+
684
+ ```python
685
+ CallawaySantAnna(
686
+ control_group='never_treated', # or 'not_yet_treated'
687
+ anticipation=0, # Periods before treatment with effects
688
+ estimation_method='dr', # 'dr', 'ipw', or 'reg'
689
+ alpha=0.05, # Significance level
690
+ cluster=None, # Column for cluster SEs
691
+ n_bootstrap=0, # Must be 0 (bootstrap not yet implemented)
692
+ seed=None # Random seed
693
+ )
694
+ ```
695
+
696
+ **Current limitations:**
697
+ - Bootstrap inference (`n_bootstrap > 0`) is not yet implemented
698
+ - Covariate adjustment for conditional parallel trends is not yet implemented
699
+
700
+ ### Event Study Visualization
701
+
702
+ Create publication-ready event study plots:
703
+
704
+ ```python
705
+ from diff_diff import plot_event_study, MultiPeriodDiD, CallawaySantAnna
706
+
707
+ # From MultiPeriodDiD
708
+ did = MultiPeriodDiD()
709
+ results = did.fit(data, outcome='y', treatment='treated',
710
+ time='period', post_periods=[3, 4, 5])
711
+ plot_event_study(results, title="Treatment Effects Over Time")
712
+
713
+ # From CallawaySantAnna (with event study aggregation)
714
+ cs = CallawaySantAnna()
715
+ results = cs.fit(data, outcome='y', unit='unit', time='period',
716
+ first_treat='first_treat', aggregate='event_study')
717
+ plot_event_study(results, title="Staggered DiD Event Study")
718
+
719
+ # From a DataFrame
720
+ df = pd.DataFrame({
721
+ 'period': [-2, -1, 0, 1, 2],
722
+ 'effect': [0.1, 0.05, 0.0, 2.5, 2.8],
723
+ 'se': [0.3, 0.25, 0.0, 0.4, 0.45]
724
+ })
725
+ plot_event_study(df, reference_period=0)
726
+
727
+ # With customization
728
+ ax = plot_event_study(
729
+ results,
730
+ title="Dynamic Treatment Effects",
731
+ xlabel="Years Relative to Treatment",
732
+ ylabel="Effect on Sales ($1000s)",
733
+ color="#2563eb",
734
+ marker="o",
735
+ shade_pre=True, # Shade pre-treatment region
736
+ show_zero_line=True, # Horizontal line at y=0
737
+ show_reference_line=True, # Vertical line at reference period
738
+ figsize=(10, 6),
739
+ show=False # Don't call plt.show(), return axes
740
+ )
741
+ ```
742
+
525
743
  ### Synthetic Difference-in-Differences
526
744
 
527
745
  Synthetic DiD combines the strengths of Difference-in-Differences and Synthetic Control methods by re-weighting control units to better match treated units' pre-treatment outcomes.
@@ -1061,6 +1279,31 @@ aggregate_to_cohorts(
1061
1279
  )
1062
1280
  ```
1063
1281
 
1282
+ #### rank_control_units
1283
+
1284
+ ```python
1285
+ rank_control_units(
1286
+ data, # Panel data in long format
1287
+ unit_column, # Unit identifier column
1288
+ time_column, # Time period column
1289
+ outcome_column, # Outcome variable column
1290
+ treatment_column=None, # Treatment indicator column (0/1)
1291
+ treated_units=None, # Explicit list of treated unit IDs
1292
+ pre_periods=None, # Pre-treatment periods (default: first half)
1293
+ covariates=None, # Covariate columns for matching
1294
+ outcome_weight=0.7, # Weight for outcome trend similarity (0-1)
1295
+ covariate_weight=0.3, # Weight for covariate distance (0-1)
1296
+ exclude_units=None, # Units to exclude from control pool
1297
+ require_units=None, # Units that must appear in output
1298
+ n_top=None, # Return only top N controls
1299
+ suggest_treatment_candidates=False, # Identify treatment candidates
1300
+ n_treatment_candidates=5, # Number of treatment candidates
1301
+ lambda_reg=0.0 # Regularization for synthetic weights
1302
+ )
1303
+ ```
1304
+
1305
+ Returns DataFrame with columns: `unit`, `quality_score`, `outcome_trend_score`, `covariate_score`, `synthetic_weight`, `pre_trend_rmse`, `is_required`.
1306
+
1064
1307
  ## Requirements
1065
1308
 
1066
1309
  - Python >= 3.9
@@ -1126,14 +1369,20 @@ This library implements methods from the following scholarly works:
1126
1369
 
1127
1370
  - **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)
1128
1371
 
1372
+ - **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)
1373
+
1129
1374
  ### Multi-Period and Staggered Adoption
1130
1375
 
1131
1376
  - **Callaway, B., & Sant'Anna, P. H. C. (2021).** "Difference-in-Differences with Multiple Time Periods." *Journal of Econometrics*, 225(2), 200-230. [https://doi.org/10.1016/j.jeconom.2020.12.001](https://doi.org/10.1016/j.jeconom.2020.12.001)
1132
1377
 
1378
+ - **Sant'Anna, P. H. C., & Zhao, J. (2020).** "Doubly Robust Difference-in-Differences Estimators." *Journal of Econometrics*, 219(1), 101-122. [https://doi.org/10.1016/j.jeconom.2020.06.003](https://doi.org/10.1016/j.jeconom.2020.06.003)
1379
+
1133
1380
  - **Sun, L., & Abraham, S. (2021).** "Estimating Dynamic Treatment Effects in Event Studies with Heterogeneous Treatment Effects." *Journal of Econometrics*, 225(2), 175-199. [https://doi.org/10.1016/j.jeconom.2020.09.006](https://doi.org/10.1016/j.jeconom.2020.09.006)
1134
1381
 
1135
1382
  - **de Chaisemartin, C., & D'Haultfœuille, X. (2020).** "Two-Way Fixed Effects Estimators with Heterogeneous Treatment Effects." *American Economic Review*, 110(9), 2964-2996. [https://doi.org/10.1257/aer.20181169](https://doi.org/10.1257/aer.20181169)
1136
1383
 
1384
+ - **Goodman-Bacon, A. (2021).** "Difference-in-Differences with Variation in Treatment Timing." *Journal of Econometrics*, 225(2), 254-277. [https://doi.org/10.1016/j.jeconom.2021.03.014](https://doi.org/10.1016/j.jeconom.2021.03.014)
1385
+
1137
1386
  ### General Causal Inference
1138
1387
 
1139
1388
  - **Imbens, G. W., & Rubin, D. B. (2015).** *Causal Inference for Statistics, Social, and Biomedical Sciences: An Introduction*. Cambridge University Press.
@@ -69,7 +69,10 @@ Signif. codes: '***' 0.001, '**' 0.01, '*' 0.05, '.' 0.1
69
69
  - **Robust inference**: Heteroskedasticity-robust (HC1) and cluster-robust standard errors
70
70
  - **Panel data support**: Two-way fixed effects estimator for panel designs
71
71
  - **Multi-period analysis**: Event-study style DiD with period-specific treatment effects
72
+ - **Staggered adoption**: Callaway-Sant'Anna (2021) estimator for heterogeneous treatment timing
72
73
  - **Synthetic DiD**: Combined DiD with synthetic control for improved robustness
74
+ - **Event study plots**: Publication-ready visualization of treatment effects
75
+ - **Parallel trends testing**: Multiple methods including equivalence tests
73
76
  - **Data prep utilities**: Helper functions for common data preparation tasks
74
77
 
75
78
  ## Data Preparation
@@ -307,6 +310,79 @@ cohort_data = aggregate_to_cohorts(
307
310
  # Result: mean outcome by treatment group and period
308
311
  ```
309
312
 
313
+ ### Rank Control Units
314
+
315
+ Select the best control units for DiD or Synthetic DiD analysis by ranking them based on pre-treatment outcome similarity:
316
+
317
+ ```python
318
+ from diff_diff import rank_control_units, generate_did_data
319
+
320
+ # Generate sample data
321
+ data = generate_did_data(n_units=50, n_periods=6, seed=42)
322
+
323
+ # Rank control units by their similarity to treated units
324
+ ranking = rank_control_units(
325
+ data,
326
+ unit_column='unit',
327
+ time_column='period',
328
+ outcome_column='outcome',
329
+ treatment_column='treated',
330
+ n_top=10 # Return top 10 controls
331
+ )
332
+
333
+ print(ranking[['unit', 'quality_score', 'pre_trend_rmse']])
334
+ ```
335
+
336
+ Output:
337
+ ```
338
+ unit quality_score pre_trend_rmse
339
+ 0 35 1.0000 0.4521
340
+ 1 42 0.9234 0.5123
341
+ 2 28 0.8876 0.5892
342
+ ...
343
+ ```
344
+
345
+ With covariates for matching:
346
+
347
+ ```python
348
+ # Add covariate-based matching
349
+ ranking = rank_control_units(
350
+ data,
351
+ unit_column='unit',
352
+ time_column='period',
353
+ outcome_column='outcome',
354
+ treatment_column='treated',
355
+ covariates=['size', 'age'], # Match on these too
356
+ outcome_weight=0.7, # 70% weight on outcome trends
357
+ covariate_weight=0.3 # 30% weight on covariate similarity
358
+ )
359
+ ```
360
+
361
+ Filter data for SyntheticDiD using top controls:
362
+
363
+ ```python
364
+ from diff_diff import SyntheticDiD
365
+
366
+ # Get top control units
367
+ top_controls = ranking['unit'].tolist()
368
+
369
+ # Filter data to treated + top controls
370
+ filtered_data = data[
371
+ (data['treated'] == 1) | (data['unit'].isin(top_controls))
372
+ ]
373
+
374
+ # Fit SyntheticDiD with selected controls
375
+ sdid = SyntheticDiD()
376
+ results = sdid.fit(
377
+ filtered_data,
378
+ outcome='outcome',
379
+ treatment='treated',
380
+ unit='unit',
381
+ time='period',
382
+ post_periods=[3, 4, 5]
383
+ )
384
+ ```
385
+
310
386
  ## Usage
311
387
 
312
388
  ### Basic DiD with Column Names
@@ -487,6 +563,148 @@ Signif. codes: '***' 0.001, '**' 0.01, '*' 0.05, '.' 0.1
487
563
  ================================================================================
488
564
  ```
489
565
 
566
+ ### Staggered Difference-in-Differences (Callaway-Sant'Anna)
567
+
568
+ When treatment is adopted at different times by different units, traditional TWFE estimators can be biased. The Callaway-Sant'Anna estimator provides unbiased estimates with staggered adoption.
569
+
570
+ ```python
571
+ from diff_diff import CallawaySantAnna
572
+
573
+ # Panel data with staggered treatment
574
+ # 'first_treat' = period when unit was first treated (0 if never treated)
575
+ cs = CallawaySantAnna()
576
+ results = cs.fit(
577
+ panel_data,
578
+ outcome='sales',
579
+ unit='firm_id',
580
+ time='year',
581
+ first_treat='first_treat', # 0 for never-treated, else first treatment year
582
+ aggregate='event_study' # Compute event study effects
583
+ )
584
+
585
+ # View results
586
+ results.print_summary()
587
+
588
+ # Access group-time effects ATT(g,t)
589
+ for (group, time), effect in results.group_time_effects.items():
590
+ print(f"Cohort {group}, Period {time}: {effect['effect']:.3f}")
591
+
592
+ # Event study effects (averaged by relative time)
593
+ for rel_time, effect in results.event_study_effects.items():
594
+ print(f"e={rel_time}: {effect['effect']:.3f} (SE: {effect['se']:.3f})")
595
+
596
+ # Convert to DataFrame
597
+ df = results.to_dataframe(level='event_study')
598
+ ```
599
+
600
+ Output:
601
+ ```
602
+ =====================================================================================
603
+ Callaway-Sant'Anna Staggered Difference-in-Differences Results
604
+ =====================================================================================
605
+
606
+ Total observations: 600
607
+ Treated units: 35
608
+ Control units: 15
609
+ Treatment cohorts: 3
610
+ Time periods: 8
611
+ Control group: never_treated
612
+
613
+ -------------------------------------------------------------------------------------
614
+ Overall Average Treatment Effect on the Treated
615
+ -------------------------------------------------------------------------------------
616
+ Parameter Estimate Std. Err. t-stat P>|t| Sig.
617
+ -------------------------------------------------------------------------------------
618
+ ATT 2.5000 0.3521 7.101 0.0000 ***
619
+ -------------------------------------------------------------------------------------
620
+
621
+ 95% Confidence Interval: [1.8099, 3.1901]
622
+
623
+ -------------------------------------------------------------------------------------
624
+ Event Study (Dynamic) Effects
625
+ -------------------------------------------------------------------------------------
626
+ Rel. Period Estimate Std. Err. t-stat P>|t| Sig.
627
+ -------------------------------------------------------------------------------------
628
+ 0 2.1000 0.4521 4.645 0.0000 ***
629
+ 1 2.5000 0.4123 6.064 0.0000 ***
630
+ 2 2.8000 0.5234 5.349 0.0000 ***
631
+ -------------------------------------------------------------------------------------
632
+
633
+ Signif. codes: '***' 0.001, '**' 0.01, '*' 0.05, '.' 0.1
634
+ =====================================================================================
635
+ ```
636
+
637
+ **When to use Callaway-Sant'Anna vs TWFE:**
638
+
639
+ | Scenario | Use TWFE | Use Callaway-Sant'Anna |
640
+ |----------|----------|------------------------|
641
+ | All units treated at same time | ✓ | ✓ |
642
+ | Staggered adoption, homogeneous effects | ✓ | ✓ |
643
+ | Staggered adoption, heterogeneous effects | ✗ | ✓ |
644
+ | Need event study with staggered timing | ✗ | ✓ |
645
+ | Fewer than ~20 treated units | ✓ | Depends on design |
646
+
647
+ **Parameters:**
648
+
649
+ ```python
650
+ CallawaySantAnna(
651
+ control_group='never_treated', # or 'not_yet_treated'
652
+ anticipation=0, # Periods before treatment with effects
653
+ estimation_method='dr', # 'dr', 'ipw', or 'reg'
654
+ alpha=0.05, # Significance level
655
+ cluster=None, # Column for cluster SEs
656
+ n_bootstrap=0, # Must be 0 (bootstrap not yet implemented)
657
+ seed=None # Random seed
658
+ )
659
+ ```
660
+
661
+ **Current limitations:**
662
+ - Bootstrap inference (`n_bootstrap > 0`) is not yet implemented
663
+ - Covariate adjustment for conditional parallel trends is not yet implemented
664
+
665
+ ### Event Study Visualization
666
+
667
+ Create publication-ready event study plots:
668
+
669
+ ```python
670
+ from diff_diff import plot_event_study, MultiPeriodDiD, CallawaySantAnna
671
+
672
+ # From MultiPeriodDiD
673
+ did = MultiPeriodDiD()
674
+ results = did.fit(data, outcome='y', treatment='treated',
675
+ time='period', post_periods=[3, 4, 5])
676
+ plot_event_study(results, title="Treatment Effects Over Time")
677
+
678
+ # From CallawaySantAnna (with event study aggregation)
679
+ cs = CallawaySantAnna()
680
+ results = cs.fit(data, outcome='y', unit='unit', time='period',
681
+ first_treat='first_treat', aggregate='event_study')
682
+ plot_event_study(results, title="Staggered DiD Event Study")
683
+
684
+ # From a DataFrame
685
+ df = pd.DataFrame({
686
+ 'period': [-2, -1, 0, 1, 2],
687
+ 'effect': [0.1, 0.05, 0.0, 2.5, 2.8],
688
+ 'se': [0.3, 0.25, 0.0, 0.4, 0.45]
689
+ })
690
+ plot_event_study(df, reference_period=0)
691
+
692
+ # With customization
693
+ ax = plot_event_study(
694
+ results,
695
+ title="Dynamic Treatment Effects",
696
+ xlabel="Years Relative to Treatment",
697
+ ylabel="Effect on Sales ($1000s)",
698
+ color="#2563eb",
699
+ marker="o",
700
+ shade_pre=True, # Shade pre-treatment region
701
+ show_zero_line=True, # Horizontal line at y=0
702
+ show_reference_line=True, # Vertical line at reference period
703
+ figsize=(10, 6),
704
+ show=False # Don't call plt.show(), return axes
705
+ )
706
+ ```
707
+
490
708
  ### Synthetic Difference-in-Differences
491
709
 
492
710
  Synthetic DiD combines the strengths of Difference-in-Differences and Synthetic Control methods by re-weighting control units to better match treated units' pre-treatment outcomes.
@@ -1026,6 +1244,31 @@ aggregate_to_cohorts(
1026
1244
  )
1027
1245
  ```
1028
1246
 
1247
+ #### rank_control_units
1248
+
1249
+ ```python
1250
+ rank_control_units(
1251
+ data, # Panel data in long format
1252
+ unit_column, # Unit identifier column
1253
+ time_column, # Time period column
1254
+ outcome_column, # Outcome variable column
1255
+ treatment_column=None, # Treatment indicator column (0/1)
1256
+ treated_units=None, # Explicit list of treated unit IDs
1257
+ pre_periods=None, # Pre-treatment periods (default: first half)
1258
+ covariates=None, # Covariate columns for matching
1259
+ outcome_weight=0.7, # Weight for outcome trend similarity (0-1)
1260
+ covariate_weight=0.3, # Weight for covariate distance (0-1)
1261
+ exclude_units=None, # Units to exclude from control pool
1262
+ require_units=None, # Units that must appear in output
1263
+ n_top=None, # Return only top N controls
1264
+ suggest_treatment_candidates=False, # Identify treatment candidates
1265
+ n_treatment_candidates=5, # Number of treatment candidates
1266
+ lambda_reg=0.0 # Regularization for synthetic weights
1267
+ )
1268
+ ```
1269
+
1270
+ Returns DataFrame with columns: `unit`, `quality_score`, `outcome_trend_score`, `covariate_score`, `synthetic_weight`, `pre_trend_rmse`, `is_required`.
1271
+
1029
1272
  ## Requirements
1030
1273
 
1031
1274
  - Python >= 3.9
@@ -1091,14 +1334,20 @@ This library implements methods from the following scholarly works:
1091
1334
 
1092
1335
  - **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)
1093
1336
 
1337
+ - **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)
1338
+
1094
1339
  ### Multi-Period and Staggered Adoption
1095
1340
 
1096
1341
  - **Callaway, B., & Sant'Anna, P. H. C. (2021).** "Difference-in-Differences with Multiple Time Periods." *Journal of Econometrics*, 225(2), 200-230. [https://doi.org/10.1016/j.jeconom.2020.12.001](https://doi.org/10.1016/j.jeconom.2020.12.001)
1097
1342
 
1343
+ - **Sant'Anna, P. H. C., & Zhao, J. (2020).** "Doubly Robust Difference-in-Differences Estimators." *Journal of Econometrics*, 219(1), 101-122. [https://doi.org/10.1016/j.jeconom.2020.06.003](https://doi.org/10.1016/j.jeconom.2020.06.003)
1344
+
1098
1345
  - **Sun, L., & Abraham, S. (2021).** "Estimating Dynamic Treatment Effects in Event Studies with Heterogeneous Treatment Effects." *Journal of Econometrics*, 225(2), 175-199. [https://doi.org/10.1016/j.jeconom.2020.09.006](https://doi.org/10.1016/j.jeconom.2020.09.006)
1099
1346
 
1100
1347
  - **de Chaisemartin, C., & D'Haultfœuille, X. (2020).** "Two-Way Fixed Effects Estimators with Heterogeneous Treatment Effects." *American Economic Review*, 110(9), 2964-2996. [https://doi.org/10.1257/aer.20181169](https://doi.org/10.1257/aer.20181169)
1101
1348
 
1349
+ - **Goodman-Bacon, A. (2021).** "Difference-in-Differences with Variation in Treatment Timing." *Journal of Econometrics*, 225(2), 254-277. [https://doi.org/10.1016/j.jeconom.2021.03.014](https://doi.org/10.1016/j.jeconom.2021.03.014)
1350
+
1102
1351
  ### General Causal Inference
1103
1352
 
1104
1353
  - **Imbens, G. W., & Rubin, D. B. (2015).** *Causal Inference for Statistics, Social, and Biomedical Sciences: An Introduction*. Cambridge University Press.
@@ -0,0 +1,80 @@
1
+ """
2
+ diff-diff: A library for Difference-in-Differences analysis.
3
+
4
+ This library provides sklearn-like estimators for causal inference
5
+ using the difference-in-differences methodology.
6
+ """
7
+
8
+ from diff_diff.estimators import (
9
+ DifferenceInDifferences,
10
+ TwoWayFixedEffects,
11
+ MultiPeriodDiD,
12
+ SyntheticDiD,
13
+ )
14
+ from diff_diff.staggered import (
15
+ CallawaySantAnna,
16
+ CallawaySantAnnaResults,
17
+ GroupTimeEffect,
18
+ )
19
+ from diff_diff.results import (
20
+ DiDResults,
21
+ MultiPeriodDiDResults,
22
+ PeriodEffect,
23
+ SyntheticDiDResults,
24
+ )
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,
40
+ )
41
+ from diff_diff.utils import (
42
+ check_parallel_trends,
43
+ check_parallel_trends_robust,
44
+ equivalence_test_trends,
45
+ )
46
+
47
+ __version__ = "0.4.0"
48
+ __all__ = [
49
+ # Estimators
50
+ "DifferenceInDifferences",
51
+ "TwoWayFixedEffects",
52
+ "MultiPeriodDiD",
53
+ "SyntheticDiD",
54
+ "CallawaySantAnna",
55
+ # Results
56
+ "DiDResults",
57
+ "MultiPeriodDiDResults",
58
+ "SyntheticDiDResults",
59
+ "PeriodEffect",
60
+ "CallawaySantAnnaResults",
61
+ "GroupTimeEffect",
62
+ # Visualization
63
+ "plot_event_study",
64
+ "plot_group_effects",
65
+ # Parallel trends testing
66
+ "check_parallel_trends",
67
+ "check_parallel_trends_robust",
68
+ "equivalence_test_trends",
69
+ # Data preparation utilities
70
+ "make_treatment_indicator",
71
+ "make_post_indicator",
72
+ "wide_to_long",
73
+ "balance_panel",
74
+ "validate_did_data",
75
+ "summarize_did_data",
76
+ "generate_did_data",
77
+ "create_event_time",
78
+ "aggregate_to_cohorts",
79
+ "rank_control_units",
80
+ ]