diffindiff 2.5.6__tar.gz → 2.5.8__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 (22) hide show
  1. {diffindiff-2.5.6 → diffindiff-2.5.8}/PKG-INFO +28 -7
  2. {diffindiff-2.5.6 → diffindiff-2.5.8}/README.md +9 -5
  3. {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/config.py +3 -3
  4. {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/didanalysis.py +100 -46
  5. {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/didanalysis_helper.py +74 -47
  6. {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/diddata.py +32 -21
  7. {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/didtools.py +60 -29
  8. {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/tests/tests_diffindiff.py +2 -3
  9. {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff.egg-info/PKG-INFO +28 -7
  10. {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff.egg-info/SOURCES.txt +2 -1
  11. {diffindiff-2.5.6 → diffindiff-2.5.8}/setup.py +1 -1
  12. diffindiff-2.5.8/tests/test_diffindiff.py +257 -0
  13. {diffindiff-2.5.6 → diffindiff-2.5.8}/MANIFEST.in +0 -0
  14. {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/__init__.py +0 -0
  15. {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/tests/__init__.py +0 -0
  16. {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/tests/data/Corona_Hesse.xlsx +0 -0
  17. {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/tests/data/counties_DE.csv +0 -0
  18. {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/tests/data/curfew_DE.csv +0 -0
  19. {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff.egg-info/dependency_links.txt +0 -0
  20. {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff.egg-info/requires.txt +0 -0
  21. {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff.egg-info/top_level.txt +0 -0
  22. {diffindiff-2.5.6 → diffindiff-2.5.8}/setup.cfg +0 -0
@@ -1,11 +1,28 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: diffindiff
3
- Version: 2.5.6
3
+ Version: 2.5.8
4
4
  Summary: diffindiff: Python library for convenient Difference-in-Differences analyses
5
5
  Author: Thomas Wieland
6
6
  Author-email: geowieland@googlemail.com
7
7
  Description-Content-Type: text/markdown
8
+ Requires-Dist: pandas
9
+ Requires-Dist: numpy
10
+ Requires-Dist: statsmodels>=0.14.5
11
+ Requires-Dist: scipy>=1.17
12
+ Requires-Dist: scikit-learn
13
+ Requires-Dist: openpyxl
14
+ Requires-Dist: matplotlib
15
+ Requires-Dist: patsy
8
16
  Provides-Extra: optional
17
+ Requires-Dist: lightgbm; extra == "optional"
18
+ Requires-Dist: xgboost; extra == "optional"
19
+ Dynamic: author
20
+ Dynamic: author-email
21
+ Dynamic: description
22
+ Dynamic: description-content-type
23
+ Dynamic: provides-extra
24
+ Dynamic: requires-dist
25
+ Dynamic: summary
9
26
 
10
27
  # diffindiff: Python library for convenient Difference-in-Differences analyses
11
28
 
@@ -23,14 +40,14 @@ Thomas Wieland [ORCID](https://orcid.org/0000-0001-5168-9846) [EMail](mailto:geo
23
40
  - 💻 GitHub Repository: [diffindiff_official](https://github.com/geowieland/diffindiff_official)
24
41
  - 📄 DOI (Zenodo): [10.5281/zenodo.18656820](https://doi.org/10.5281/zenodo.18656820)
25
42
 
26
- A case study that utilizes the diffindiff library is available on [arXiv](https://arxiv.org/abs/2605.21464).
43
+ A case study that utilizes the diffindiff library (preprint) is available on [arXiv](https://arxiv.org/abs/2605.21464).
27
44
 
28
45
 
29
46
  ## Citation
30
47
 
31
48
  If you use this software, please cite:
32
49
 
33
- Wieland, T. (2026). diffindiff: A Python library for convenient difference-in-differences analyses (Version 2.5.6) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.18656820
50
+ Wieland, T. (2026). diffindiff: A Python library for convenient difference-in-differences analyses (Version 2.5.8) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.18656820
34
51
 
35
52
 
36
53
  ## Installation
@@ -181,8 +198,12 @@ See the /tests directory for usage examples of most of the included functions.
181
198
  This software was developed without the use of AI-generated code. The GitHub Copilot Chat in Microsoft Visual Studio Code using the GPT-5 mini model (by OpenAI) was used solely to assist in drafting and refining docstrings for documentation. The corresponding guidelines and constraints defined by the author are documented in `AGENTS-docstrings.md` in the [public GitHub repository](https://github.com/geowieland/diffindiff_official).
182
199
 
183
200
 
184
- ## What's new (v2.5.6)
201
+ ## What's new (v2.5.8)
185
202
 
186
203
  - Bugfixes
187
- - didtools.is_parallel(): Checking whether there is a pre-treatment period and skipping test if not (additional NOTE)
188
- - Optional installation of XGBoost and LightGBM (more stable if problems with the installation of these packages occur)
204
+ - didtools.is_balanced() now conducts a truly comprehensive check whether the input panel data is balanced
205
+ - Fixed bug in didtools.is_numeric() with respect to unintentional modification of the dataset
206
+ - diddata.create_counterfactual() now catches error if no model was trained by didtools.model_wrapper()
207
+ - didanalysis.DiffModel.plot_treatment_effects() now treats expections correctly
208
+ - Other:
209
+ - verbose option in any function which prints NOTEs, with all NOTEs being disabled when verbose = False
@@ -14,14 +14,14 @@ Thomas Wieland [ORCID](https://orcid.org/0000-0001-5168-9846) [EMail](mailto:geo
14
14
  - 💻 GitHub Repository: [diffindiff_official](https://github.com/geowieland/diffindiff_official)
15
15
  - 📄 DOI (Zenodo): [10.5281/zenodo.18656820](https://doi.org/10.5281/zenodo.18656820)
16
16
 
17
- A case study that utilizes the diffindiff library is available on [arXiv](https://arxiv.org/abs/2605.21464).
17
+ A case study that utilizes the diffindiff library (preprint) is available on [arXiv](https://arxiv.org/abs/2605.21464).
18
18
 
19
19
 
20
20
  ## Citation
21
21
 
22
22
  If you use this software, please cite:
23
23
 
24
- Wieland, T. (2026). diffindiff: A Python library for convenient difference-in-differences analyses (Version 2.5.6) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.18656820
24
+ Wieland, T. (2026). diffindiff: A Python library for convenient difference-in-differences analyses (Version 2.5.8) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.18656820
25
25
 
26
26
 
27
27
  ## Installation
@@ -172,8 +172,12 @@ See the /tests directory for usage examples of most of the included functions.
172
172
  This software was developed without the use of AI-generated code. The GitHub Copilot Chat in Microsoft Visual Studio Code using the GPT-5 mini model (by OpenAI) was used solely to assist in drafting and refining docstrings for documentation. The corresponding guidelines and constraints defined by the author are documented in `AGENTS-docstrings.md` in the [public GitHub repository](https://github.com/geowieland/diffindiff_official).
173
173
 
174
174
 
175
- ## What's new (v2.5.6)
175
+ ## What's new (v2.5.8)
176
176
 
177
177
  - Bugfixes
178
- - didtools.is_parallel(): Checking whether there is a pre-treatment period and skipping test if not (additional NOTE)
179
- - Optional installation of XGBoost and LightGBM (more stable if problems with the installation of these packages occur)
178
+ - didtools.is_balanced() now conducts a truly comprehensive check whether the input panel data is balanced
179
+ - Fixed bug in didtools.is_numeric() with respect to unintentional modification of the dataset
180
+ - diddata.create_counterfactual() now catches error if no model was trained by didtools.model_wrapper()
181
+ - didanalysis.DiffModel.plot_treatment_effects() now treats expections correctly
182
+ - Other:
183
+ - verbose option in any function which prints NOTEs, with all NOTEs being disabled when verbose = False
@@ -4,15 +4,15 @@
4
4
  # Author: Thomas Wieland
5
5
  # ORCID: 0000-0001-5168-9846
6
6
  # mail: geowieland@googlemail.com
7
- # Version: 1.0.27
8
- # Last update: 2026-10-03 11:03
7
+ # Version: 1.0.29
8
+ # Last update: 2026-10-17 21:17
9
9
  # Copyright (c) 2025-2026 Thomas Wieland
10
10
  #-----------------------------------------------------------------------
11
11
 
12
12
  # Basic config:
13
13
 
14
14
  PACKAGE_NAME = "diffindiff"
15
- PACKAGE_VERSION = "2.5.6"
15
+ PACKAGE_VERSION = "2.5.8"
16
16
 
17
17
  VERBOSE = False
18
18
 
@@ -4,8 +4,8 @@
4
4
  # Author: Thomas Wieland
5
5
  # ORCID: 0000-0001-5168-9846
6
6
  # mail: geowieland@googlemail.com
7
- # Version: 2.4.6
8
- # Last update: 2026-09-11 21:46
7
+ # Version: 2.4.8
8
+ # Last update: 2026-10-17 21:15
9
9
  # Copyright (c) 2024-2026 Thomas Wieland
10
10
  #-----------------------------------------------------------------------
11
11
 
@@ -111,7 +111,8 @@ class DiffModel:
111
111
  def treatment_statistics(
112
112
  self,
113
113
  treatment: str = None,
114
- after_treatment_col: str = None
114
+ after_treatment_col: str = None,
115
+ verbose: bool = False
115
116
  ):
116
117
 
117
118
  """
@@ -163,7 +164,9 @@ class DiffModel:
163
164
  else:
164
165
 
165
166
  treatment = model_config["treatment_col"][0]
166
- print(f"NOTE: No treatment was stated. Choosing treatment '{treatment}' for calculating treatment statistics.")
167
+
168
+ if verbose:
169
+ print(f"NOTE: No treatment was stated. Choosing treatment '{treatment}' for calculating treatment statistics.")
167
170
 
168
171
  if after_treatment_col is not None:
169
172
 
@@ -782,9 +785,9 @@ class DiffModel:
782
785
 
783
786
  if len(no_control_conditions) > 0:
784
787
  if len(no_control_conditions) == 1:
785
- print(f"NOTE: Treatment '{no_control_conditions[0]}' has no control conditions.")
788
+ print(f"WARNING: Treatment '{no_control_conditions[0]}' has no control conditions.")
786
789
  else:
787
- print(f"NOTE: Treatments {', '.join(no_control_conditions)} have no control conditions.")
790
+ print(f"WARNING: Treatments {', '.join(no_control_conditions)} have no control conditions.")
788
791
 
789
792
  print("-" * int(total_width))
790
793
 
@@ -905,6 +908,7 @@ class DiffModel:
905
908
  treatment_effects = self.treatment_effects()
906
909
 
907
910
  try:
911
+
908
912
  if model_config.get("pre_post", False) and len(model_config.get("after_treatment_col", [])) > 0:
909
913
 
910
914
  labels = treatment_effects.iloc[:, 0].astype(str).values
@@ -943,9 +947,11 @@ class DiffModel:
943
947
  break
944
948
  if appended:
945
949
  break
946
- except Exception:
950
+ except Exception as e:
947
951
 
948
- pass
952
+ print(f"Plot of treatment effects not possible: {str(e)}")
953
+
954
+ return self
949
955
 
950
956
  if sort_by_coef:
951
957
  treatment_effects = treatment_effects.sort_values(
@@ -1099,7 +1105,8 @@ class DiffModel:
1099
1105
  self,
1100
1106
  treatment = None,
1101
1107
  after_treatment_col: str = None,
1102
- retransform_log_outcome: bool = False
1108
+ retransform_log_outcome: bool = False,
1109
+ verbose: bool = False
1103
1110
  ):
1104
1111
 
1105
1112
  """
@@ -1153,8 +1160,11 @@ class DiffModel:
1153
1160
  if treatment not in model_config["treatment_col"]:
1154
1161
  raise ValueError (f"Treatment '{treatment}' not in model object.")
1155
1162
  else:
1163
+
1156
1164
  treatment = model_config["treatment_col"][0]
1157
- print(f"NOTE: No treatment was stated. Choosing treatment '{treatment}' for calculating counterfactual.")
1165
+
1166
+ if verbose:
1167
+ print(f"NOTE: No treatment was stated. Choosing treatment '{treatment}' for calculating counterfactual.")
1158
1168
 
1159
1169
  if after_treatment_col is not None:
1160
1170
  if len(model_config["after_treatment_col"]) == 0:
@@ -1313,7 +1323,8 @@ class DiffModel:
1313
1323
  TT_col: str = None,
1314
1324
  divide: float = 0.5,
1315
1325
  resample: float = 1.0,
1316
- random_state = 71
1326
+ random_state = 71,
1327
+ verbose: bool = False
1317
1328
  ):
1318
1329
 
1319
1330
  """
@@ -1369,8 +1380,11 @@ class DiffModel:
1369
1380
  if treatment not in model_config["treatment_col"]:
1370
1381
  raise ValueError (f"Treatment '{treatment}' not in model object.")
1371
1382
  else:
1383
+
1372
1384
  treatment = model_config["treatment_col"][0]
1373
- print(f"NOTE: No treatment was stated. Choosing treatment '{treatment}' for placebo analysis.")
1385
+
1386
+ if verbose:
1387
+ print(f"NOTE: No treatment was stated. Choosing treatment '{treatment}' for placebo analysis.")
1374
1388
 
1375
1389
  if after_treatment_col is not None:
1376
1390
  if len(model_config["after_treatment_col"]) == 0:
@@ -1390,7 +1404,8 @@ class DiffModel:
1390
1404
  treatment_demeaned = treatment
1391
1405
  treatment = treatment.replace(f"{config.DELIMITER}{config.DEMEAN_SUFFIX}", "")
1392
1406
 
1393
- print(f"NOTE: Data is demeaned. Treatment statistics are calculated based on original treatment '{treatment}'.")
1407
+ if verbose:
1408
+ print(f"NOTE: Data is demeaned. Treatment statistics are calculated based on original treatment '{treatment}'.")
1394
1409
 
1395
1410
  treatment_statistics = self.treatment_statistics(treatment = treatment)
1396
1411
 
@@ -1484,7 +1499,8 @@ class DiffModel:
1484
1499
  plot_symbol: str = "o",
1485
1500
  treatment_group_only: bool = True,
1486
1501
  save_fig: str = None,
1487
- save_fig_kwargs: dict = {}
1502
+ save_fig_kwargs: dict = {},
1503
+ verbose: bool = False
1488
1504
  ):
1489
1505
 
1490
1506
  """
@@ -1542,8 +1558,11 @@ class DiffModel:
1542
1558
  if treatment not in model_config["treatment_col"]:
1543
1559
  raise ValueError (f"Treatment '{treatment}' not in model object.")
1544
1560
  else:
1561
+
1545
1562
  treatment = model_config["treatment_col"][0]
1546
- print(f"NOTE: No treatment was stated. Choosing treatment '{treatment}' for plotting timeline.")
1563
+
1564
+ if verbose:
1565
+ print(f"NOTE: No treatment was stated. Choosing treatment '{treatment}' for plotting timeline.")
1547
1566
 
1548
1567
  if treatment_group_only:
1549
1568
  if TG_col is None:
@@ -1615,7 +1634,8 @@ class DiffModel:
1615
1634
  pre_post_bar_width = 0.5,
1616
1635
  retransform_log_outcome: bool = False,
1617
1636
  save_fig: str = None,
1618
- save_fig_kwargs: dict = {}
1637
+ save_fig_kwargs: dict = {},
1638
+ verbose: bool = False
1619
1639
  ):
1620
1640
 
1621
1641
  """
@@ -1723,7 +1743,8 @@ class DiffModel:
1723
1743
  break
1724
1744
  else:
1725
1745
 
1726
- print(f"NOTE: No treatment was stated. Choosing treatment '{treatment_diagnostics[0]['treatment']}' for plotting.")
1746
+ if verbose:
1747
+ print(f"NOTE: No treatment was stated. Choosing treatment '{treatment_diagnostics[0]['treatment']}' for plotting.")
1727
1748
 
1728
1749
  treatment_group = treatment_diagnostics[0]["treatment_group"]
1729
1750
  treatment = treatment_diagnostics[0]["treatment"]
@@ -1783,13 +1804,15 @@ class DiffModel:
1783
1804
  model_data[config.PREDICTIONS_SUMMARY_FRAME_COLS_LIST[4]] = np.exp(model_data[config.PREDICTIONS_SUMMARY_FRAME_COLS_LIST[4]])
1784
1805
  model_data[config.PREDICTIONS_SUMMARY_FRAME_COLS_LIST[5]] = np.exp(model_data[config.PREDICTIONS_SUMMARY_FRAME_COLS_LIST[5]])
1785
1806
 
1786
- if model_config["demean"]:
1787
- print(f"NOTE: Outcome variable '{outcome_col.replace(f'{config.DELIMITER}{config.DEMEAN_SUFFIX}', '')}' was log-transformed and demeaned. Re-transformation was applied on '{outcome_col}'.")
1788
- else:
1789
- print(f"NOTE: Outcome variable '{outcome_col}' was log-transformed. Re-transformation was applied.")
1807
+ if verbose:
1808
+ if model_config["demean"]:
1809
+ print(f"NOTE: Outcome variable '{outcome_col.replace(f'{config.DELIMITER}{config.DEMEAN_SUFFIX}', '')}' was log-transformed and demeaned. Re-transformation was applied on '{outcome_col}'.")
1810
+ else:
1811
+ print(f"NOTE: Outcome variable '{outcome_col}' was log-transformed. Re-transformation was applied.")
1790
1812
 
1791
1813
  else:
1792
- print("NOTE: Parameter 'retransform_log_outcome' was set to True, but outcome variable was not log-transformed. No re-transformation applied.")
1814
+ if verbose:
1815
+ print("NOTE: Parameter 'retransform_log_outcome' was set to True, but outcome variable was not log-transformed. No re-transformation applied.")
1793
1816
 
1794
1817
  model_data_TG = model_data[model_data[TG_col] == 1]
1795
1818
  model_data_CG = model_data[model_data[TG_col] == 0]
@@ -2096,7 +2119,8 @@ class DiffModel:
2096
2119
  plot_size: list = [12, 6],
2097
2120
  retransform_log_outcome: bool = False,
2098
2121
  save_fig: str = None,
2099
- save_fig_kwargs: dict = {}
2122
+ save_fig_kwargs: dict = {},
2123
+ verbose: bool = False
2100
2124
  ):
2101
2125
 
2102
2126
  """
@@ -2199,7 +2223,9 @@ class DiffModel:
2199
2223
  break
2200
2224
 
2201
2225
  else:
2202
- print(f"NOTE: No treatment was stated. Choosing treatment '{treatment_diagnostics[0]['treatment']}' for plotting counterfactual.")
2226
+
2227
+ if verbose:
2228
+ print(f"NOTE: No treatment was stated. Choosing treatment '{treatment_diagnostics[0]['treatment']}' for plotting counterfactual.")
2203
2229
 
2204
2230
  treatment_group = treatment_diagnostics[0]["treatment_group"]
2205
2231
 
@@ -2527,8 +2553,11 @@ def did_analysis(
2527
2553
  TG_col = []
2528
2554
 
2529
2555
  if not FE_unit and config.AUTO_SWITCH_TO_FIXED_EFFECTS:
2556
+
2530
2557
  FE_unit = True
2531
- print(f"NOTE: Quasi-experiment includes more than one treatment ({no_treatments}). Unit fixed effects are used instead of control group baseline and treatment group deviation.")
2558
+
2559
+ if verbose:
2560
+ print(f"NOTE: Quasi-experiment includes more than one treatment ({no_treatments}). Unit fixed effects are used instead of control group baseline and treatment group deviation.")
2532
2561
 
2533
2562
  if unique_units < config.FIXED_EFFECTS_THRESHOLD:
2534
2563
 
@@ -2538,7 +2567,8 @@ def did_analysis(
2538
2567
  ITT = False
2539
2568
  intercept = True
2540
2569
 
2541
- print(f"NOTE: Quasi-experiment includes less than {config.FIXED_EFFECTS_THRESHOLD} units. Unit or group fixed effects are skipped.")
2570
+ if verbose:
2571
+ print(f"NOTE: Quasi-experiment includes less than {config.FIXED_EFFECTS_THRESHOLD} units. Unit or group fixed effects are skipped.")
2542
2572
 
2543
2573
  if unique_time_points < config.FIXED_EFFECTS_THRESHOLD:
2544
2574
 
@@ -2547,7 +2577,8 @@ def did_analysis(
2547
2577
  GTT = False
2548
2578
  intercept = True
2549
2579
 
2550
- print(f"NOTE: Quasi-experiment includes less than {config.FIXED_EFFECTS_THRESHOLD} time points. Time fixed effects are skipped.")
2580
+ if verbose:
2581
+ print(f"NOTE: Quasi-experiment includes less than {config.FIXED_EFFECTS_THRESHOLD} time points. Time fixed effects are skipped.")
2551
2582
 
2552
2583
  if demean and FE_unit:
2553
2584
 
@@ -2556,38 +2587,56 @@ def did_analysis(
2556
2587
  ITE = False
2557
2588
  ITT = False
2558
2589
 
2559
- print(f"NOTE: Unit fixed effects are specified to be replaced by demeaning. {config.EFFECTS_TYPES['ITE']['description']} or {config.EFFECTS_TYPES['ITT']['description']} are not supported.")
2590
+ if verbose:
2591
+ print(f"NOTE: Unit fixed effects are specified to be replaced by demeaning. {config.EFFECTS_TYPES['ITE']['description']} or {config.EFFECTS_TYPES['ITT']['description']} are not supported.")
2560
2592
 
2561
2593
  if ITE:
2562
2594
 
2563
2595
  if not FE_unit:
2596
+
2564
2597
  FE_unit = True
2565
- print("NOTE: Model includes individual treatment effects. Unit fixed effects are included.")
2598
+
2599
+ if verbose:
2600
+ print("NOTE: Model includes individual treatment effects. Unit fixed effects are included.")
2566
2601
 
2567
2602
  if GTE:
2603
+
2568
2604
  GTE = False
2569
- print("NOTE: Both group and individual treatment effects were stated. Switching to individual treatment effects only.")
2605
+
2606
+ if verbose:
2607
+ print("NOTE: Both group and individual treatment effects were stated. Switching to individual treatment effects only.")
2570
2608
 
2571
2609
  if ITT:
2572
2610
 
2573
2611
  TT_col = []
2574
2612
 
2575
2613
  if not FE_unit:
2614
+
2576
2615
  FE_unit = True
2577
- print("NOTE: Model includes individual time trends. Unit fixed effects are included. Treatment time variable is dropped.")
2616
+
2617
+ if verbose:
2618
+ print("NOTE: Model includes individual time trends. Unit fixed effects are included. Treatment time variable is dropped.")
2578
2619
 
2579
2620
  if FE_time:
2621
+
2580
2622
  FE_time = False
2581
- print("NOTE: Model includes individual time trends. Time fixed effects are dropped.")
2623
+
2624
+ if verbose:
2625
+ print("NOTE: Model includes individual time trends. Time fixed effects are dropped.")
2582
2626
 
2583
2627
  if GTT:
2628
+
2584
2629
  GTT = False
2585
- print("NOTE: Both group and individual time trends were stated. Switching to individual time trends only.")
2630
+
2631
+ if verbose:
2632
+ print("NOTE: Both group and individual time trends were stated. Switching to individual time trends only.")
2586
2633
 
2587
2634
  if staggered_adoption and config.AUTO_SWITCH_TO_FIXED_EFFECTS:
2588
2635
 
2589
2636
  if not FE_unit or not FE_time:
2590
- print("NOTE: Quasi-experiment includes one or more staggered treatments. Two-way fixed effects model is used.")
2637
+
2638
+ if verbose:
2639
+ print("NOTE: Quasi-experiment includes one or more staggered treatments. Two-way fixed effects model is used.")
2591
2640
 
2592
2641
  FE_unit = True
2593
2642
  FE_time = True
@@ -2604,7 +2653,9 @@ def did_analysis(
2604
2653
  if FE_group:
2605
2654
 
2606
2655
  if intercept or len(TG_col) > 0:
2607
- print("NOTE: Quasi-experiment includes group fixed effects. Control group baseline and treatment group deviation are dropped.")
2656
+
2657
+ if verbose:
2658
+ print("NOTE: Quasi-experiment includes group fixed effects. Control group baseline and treatment group deviation are dropped.")
2608
2659
 
2609
2660
  TG_col = []
2610
2661
  intercept = False
@@ -2716,13 +2767,14 @@ def did_analysis(
2716
2767
 
2717
2768
  if config.AUTO_SKIP_CONSTANT_COLUMNS:
2718
2769
 
2719
- print(f"NOTE: The following columns are constant and dropped from the analysis: {', '.join(data_diagnostics['cols_constants'])}.")
2770
+ print(f"WARNING: The following columns are constant and dropped from the analysis: {', '.join(data_diagnostics['cols_constants'])}.")
2720
2771
 
2721
2772
  covariates = [col for col in covariates if col not in data_diagnostics["cols_constants"]]
2722
2773
 
2723
2774
  if data_diagnostics["is_prepost"] and config.AUTO_SWITCH_TO_PREPOST:
2724
2775
 
2725
- print(f"NOTE: Input is {config.PREPOST_PANELDATA_DESCRIPTION}. Data processing and model estimation will treat data as pre-post.")
2776
+ if verbose:
2777
+ print(f"NOTE: Input is {config.PREPOST_PANELDATA_DESCRIPTION}. Data processing and model estimation will treat data as pre-post.")
2726
2778
 
2727
2779
  pre_post = True
2728
2780
 
@@ -2730,8 +2782,8 @@ def did_analysis(
2730
2782
  FE_time = False
2731
2783
  FE_group = False
2732
2784
 
2733
- if not data_diagnostics["is_prepost"] and not FE_unit and not FE_time:
2734
- print(f"NOTE: Input is {config.MULTIPERIOD_PANELDATA_DESCRIPTION}. Consider including {config.EFFECTS_TYPES['FE']['description']}.")
2785
+ if not data_diagnostics["is_prepost"] and not FE_unit and not FE_time:
2786
+ print(f"WARNING: Input is {config.MULTIPERIOD_PANELDATA_DESCRIPTION}. Consider including {config.EFFECTS_TYPES['FE']['description']}.")
2735
2787
 
2736
2788
  if log_outcome:
2737
2789
 
@@ -2999,9 +3051,8 @@ def did_analysis(
2999
3051
  indep_vars_no = indep_vars_no-(len(FE_unit_vars)+len(FE_time_vars))
3000
3052
 
3001
3053
  if verbose:
3002
- print("OK")
3003
-
3004
- print(f"NOTE: Outcome variable '{outcome_col}' is replaced by demeaned variable '{outcome_col_demeaned}', and {len(treatment_col)+len(covariates)+len(spillover_vars)+len(interactions)} other variables were remeaned.")
3054
+ print("OK")
3055
+ print(f"NOTE: Outcome variable '{outcome_col}' is replaced by demeaned variable '{outcome_col_demeaned}', and {len(treatment_col)+len(covariates)+len(spillover_vars)+len(interactions)} other variables were remeaned.")
3005
3056
 
3006
3057
  outcome_col = outcome_col_demeaned
3007
3058
 
@@ -3283,13 +3334,15 @@ def ddd_analysis(
3283
3334
 
3284
3335
  FE_unit = False
3285
3336
 
3286
- print(f"NOTE: Quasi-experiment includes less than {config.FIXED_EFFECTS_THRESHOLD} units. Unit fixed effects are skipped.")
3337
+ if verbose:
3338
+ print(f"NOTE: Quasi-experiment includes less than {config.FIXED_EFFECTS_THRESHOLD} units. Unit fixed effects are skipped.")
3287
3339
 
3288
3340
  if unique_time_points < config.FIXED_EFFECTS_THRESHOLD:
3289
3341
 
3290
3342
  FE_time = False
3291
3343
 
3292
- print(f"NOTE: Quasi-experiment includes less than {config.FIXED_EFFECTS_THRESHOLD} time points. Time fixed effects are skipped.")
3344
+ if verbose:
3345
+ print(f"NOTE: Quasi-experiment includes less than {config.FIXED_EFFECTS_THRESHOLD} time points. Time fixed effects are skipped.")
3293
3346
 
3294
3347
  if FE_unit:
3295
3348
  TG_col = None
@@ -3320,13 +3373,14 @@ def ddd_analysis(
3320
3373
 
3321
3374
  if config.AUTO_SKIP_CONSTANT_COLUMNS:
3322
3375
 
3323
- print(f"NOTE: The following columns are constant and dropped from the analysis: {', '.join(data_diagnostics['cols_constants'])}.")
3376
+ print(f"WARNING: The following columns are constant and dropped from the analysis: {', '.join(data_diagnostics['cols_constants'])}.")
3324
3377
 
3325
3378
  covariates = [col for col in covariates if col not in data_diagnostics["cols_constants"]]
3326
3379
 
3327
3380
  if data_diagnostics["is_prepost"] and config.AUTO_SWITCH_TO_PREPOST:
3328
3381
 
3329
- print("NOTE: Panel data is pre-post. Data processing and model estimation will treat data as pre-post")
3382
+ if verbose:
3383
+ print("NOTE: Panel data is pre-post. Data processing and model estimation will treat data as pre-post")
3330
3384
 
3331
3385
  pre_post = True
3332
3386