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.
- {diffindiff-2.5.6 → diffindiff-2.5.8}/PKG-INFO +28 -7
- {diffindiff-2.5.6 → diffindiff-2.5.8}/README.md +9 -5
- {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/config.py +3 -3
- {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/didanalysis.py +100 -46
- {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/didanalysis_helper.py +74 -47
- {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/diddata.py +32 -21
- {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/didtools.py +60 -29
- {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/tests/tests_diffindiff.py +2 -3
- {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff.egg-info/PKG-INFO +28 -7
- {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff.egg-info/SOURCES.txt +2 -1
- {diffindiff-2.5.6 → diffindiff-2.5.8}/setup.py +1 -1
- diffindiff-2.5.8/tests/test_diffindiff.py +257 -0
- {diffindiff-2.5.6 → diffindiff-2.5.8}/MANIFEST.in +0 -0
- {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/__init__.py +0 -0
- {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/tests/__init__.py +0 -0
- {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/tests/data/Corona_Hesse.xlsx +0 -0
- {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/tests/data/counties_DE.csv +0 -0
- {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff/tests/data/curfew_DE.csv +0 -0
- {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff.egg-info/dependency_links.txt +0 -0
- {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff.egg-info/requires.txt +0 -0
- {diffindiff-2.5.6 → diffindiff-2.5.8}/diffindiff.egg-info/top_level.txt +0 -0
- {diffindiff-2.5.6 → diffindiff-2.5.8}/setup.cfg +0 -0
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: diffindiff
|
|
3
|
-
Version: 2.5.
|
|
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.
|
|
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.
|
|
201
|
+
## What's new (v2.5.8)
|
|
185
202
|
|
|
186
203
|
- Bugfixes
|
|
187
|
-
- didtools.
|
|
188
|
-
-
|
|
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.
|
|
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.
|
|
175
|
+
## What's new (v2.5.8)
|
|
176
176
|
|
|
177
177
|
- Bugfixes
|
|
178
|
-
- didtools.
|
|
179
|
-
-
|
|
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.
|
|
8
|
-
# Last update: 2026-10-
|
|
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.
|
|
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.
|
|
8
|
-
# Last update: 2026-
|
|
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
|
-
|
|
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"
|
|
788
|
+
print(f"WARNING: Treatment '{no_control_conditions[0]}' has no control conditions.")
|
|
786
789
|
else:
|
|
787
|
-
print(f"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
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
|
-
|
|
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"
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
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
|
-
|
|
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
|
|