diffindiff 2.5.3__tar.gz → 2.5.4__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.3 → diffindiff-2.5.4}/PKG-INFO +10 -6
- {diffindiff-2.5.3 → diffindiff-2.5.4}/README.md +9 -5
- {diffindiff-2.5.3 → diffindiff-2.5.4}/diffindiff/config.py +5 -3
- {diffindiff-2.5.3 → diffindiff-2.5.4}/diffindiff/didanalysis.py +3 -3
- {diffindiff-2.5.3 → diffindiff-2.5.4}/diffindiff/didanalysis_helper.py +104 -19
- {diffindiff-2.5.3 → diffindiff-2.5.4}/diffindiff/diddata.py +16 -5
- {diffindiff-2.5.3 → diffindiff-2.5.4}/diffindiff/tests/tests_diffindiff.py +90 -6
- {diffindiff-2.5.3 → diffindiff-2.5.4}/diffindiff.egg-info/PKG-INFO +10 -6
- {diffindiff-2.5.3 → diffindiff-2.5.4}/diffindiff.egg-info/requires.txt +0 -4
- {diffindiff-2.5.3 → diffindiff-2.5.4}/setup.py +1 -5
- {diffindiff-2.5.3 → diffindiff-2.5.4}/MANIFEST.in +0 -0
- {diffindiff-2.5.3 → diffindiff-2.5.4}/diffindiff/__init__.py +0 -0
- {diffindiff-2.5.3 → diffindiff-2.5.4}/diffindiff/didtools.py +0 -0
- {diffindiff-2.5.3 → diffindiff-2.5.4}/diffindiff/tests/__init__.py +0 -0
- {diffindiff-2.5.3 → diffindiff-2.5.4}/diffindiff/tests/data/Corona_Hesse.xlsx +0 -0
- {diffindiff-2.5.3 → diffindiff-2.5.4}/diffindiff/tests/data/counties_DE.csv +0 -0
- {diffindiff-2.5.3 → diffindiff-2.5.4}/diffindiff/tests/data/curfew_DE.csv +0 -0
- {diffindiff-2.5.3 → diffindiff-2.5.4}/diffindiff.egg-info/SOURCES.txt +0 -0
- {diffindiff-2.5.3 → diffindiff-2.5.4}/diffindiff.egg-info/dependency_links.txt +0 -0
- {diffindiff-2.5.3 → diffindiff-2.5.4}/diffindiff.egg-info/top_level.txt +0 -0
- {diffindiff-2.5.3 → diffindiff-2.5.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: diffindiff
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.4
|
|
4
4
|
Summary: diffindiff: Python library for convenient Difference-in-Differences analyses
|
|
5
5
|
Author: Thomas Wieland
|
|
6
6
|
Author-email: geowieland@googlemail.com
|
|
@@ -29,7 +29,7 @@ A case study that utilizes the diffindiff library is available on [arXiv](https:
|
|
|
29
29
|
|
|
30
30
|
If you use this software, please cite:
|
|
31
31
|
|
|
32
|
-
Wieland, T. (2026). diffindiff: A Python library for convenient difference-in-differences analyses (Version 2.5.
|
|
32
|
+
Wieland, T. (2026). diffindiff: A Python library for convenient difference-in-differences analyses (Version 2.5.4) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.18656820
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
## Installation
|
|
@@ -164,6 +164,7 @@ See the /tests directory for usage examples of most of the included functions.
|
|
|
164
164
|
- Goldfarb A, Tucker C, Wang Y (2022) Conducting Research in Marketing with Quasi-Experiments. *Journal of Marketing* 86(3): 1-19. [10.1177/00222429221082977](https://doi.org/10.1177/00222429221082977)
|
|
165
165
|
- Isporhing IE, Lipfert M, Pestel N (2021) Does re-opening schools contribute to the spread of SARS-CoV-2? Evidence from staggered summer breaks in Germany. *Journal of Public Economics* 198: 104426. [10.1016/j.jpubeco.2021.104426](https://doi.org/10.1016/j.jpubeco.2021.104426)
|
|
166
166
|
- Li KT, Luo L, Pattabhiramaiah A (2024) Causal Inference with Quasi-Experimental Data. *IMPACT at JMR* November 13, 2024. [AMA](https://www.ama.org/marketing-news/causal-inference-with-quasi-experimental-data/)
|
|
167
|
+
- Mitze T, Kosfeld R, Rode J, Wälde K (2020) Face masks considerably reduce COVID-19 cases in Germany. *Proceedings of the National Academy of Sciences of the United States of America* 117(51): 32293-32301. [10.1073/pnas.2015954117](https://doi.org/10.1073/pnas.2015954117)
|
|
167
168
|
- Olden A (2018) What do you buy when no one's watching? The effect of self-service checkouts on the composition of sales in retail. Discussion paper FOR 3/18, Norwegian School of Economics, Norway. [http://hdl.handle.net/11250/2490886](http://hdl.handle.net/11250/2490886)
|
|
168
169
|
- Olden A, Moen J (2022) The triple difference estimator. *The Econometrics Journal* 25(3): 531-553. [10.1093/ectj/utac010](https://doi.org/10.1093/ectj/utac010)
|
|
169
170
|
- Strassmann A, Çolak Y, Serra-Burriel M, Nordestgaard BG, Turk A, Afzal S, Puhan MA (2023) Nationwide indoor smoking ban and impact on smoking behaviour and lung function: a two-population natural experiment. *Thorax* 78(2): 144-150. [10.1136/thoraxjnl-2021-218436](https://doi.org/10.1136/thoraxjnl-2021-218436)
|
|
@@ -179,9 +180,12 @@ See the /tests directory for usage examples of most of the included functions.
|
|
|
179
180
|
This software was developed without the use of AI-generated code. The Continue Agent 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).
|
|
180
181
|
|
|
181
182
|
|
|
182
|
-
## What's new (v2.5.
|
|
183
|
+
## What's new (v2.5.4)
|
|
183
184
|
|
|
184
|
-
- Extensions
|
|
185
|
-
- Added optional saving of plots in DiffModel plot methods
|
|
186
185
|
- Bugfixes
|
|
187
|
-
-
|
|
186
|
+
- Correction of the nonsensical skipping of the treatment group dummy in the case of two observation units in didanalysis.did_analysis()
|
|
187
|
+
- Automatical drop of duplicates in results dictionaries in didanalysis_helper.extract_model_results()
|
|
188
|
+
- Inefficient successive data insert in diddata.DiffData.add_synthetic() replaced by efficient concatenating
|
|
189
|
+
- Other
|
|
190
|
+
- Test script extended by another example
|
|
191
|
+
- Cleaned dependencies
|
|
@@ -21,7 +21,7 @@ A case study that utilizes the diffindiff library is available on [arXiv](https:
|
|
|
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.4) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.18656820
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
## Installation
|
|
@@ -156,6 +156,7 @@ See the /tests directory for usage examples of most of the included functions.
|
|
|
156
156
|
- Goldfarb A, Tucker C, Wang Y (2022) Conducting Research in Marketing with Quasi-Experiments. *Journal of Marketing* 86(3): 1-19. [10.1177/00222429221082977](https://doi.org/10.1177/00222429221082977)
|
|
157
157
|
- Isporhing IE, Lipfert M, Pestel N (2021) Does re-opening schools contribute to the spread of SARS-CoV-2? Evidence from staggered summer breaks in Germany. *Journal of Public Economics* 198: 104426. [10.1016/j.jpubeco.2021.104426](https://doi.org/10.1016/j.jpubeco.2021.104426)
|
|
158
158
|
- Li KT, Luo L, Pattabhiramaiah A (2024) Causal Inference with Quasi-Experimental Data. *IMPACT at JMR* November 13, 2024. [AMA](https://www.ama.org/marketing-news/causal-inference-with-quasi-experimental-data/)
|
|
159
|
+
- Mitze T, Kosfeld R, Rode J, Wälde K (2020) Face masks considerably reduce COVID-19 cases in Germany. *Proceedings of the National Academy of Sciences of the United States of America* 117(51): 32293-32301. [10.1073/pnas.2015954117](https://doi.org/10.1073/pnas.2015954117)
|
|
159
160
|
- Olden A (2018) What do you buy when no one's watching? The effect of self-service checkouts on the composition of sales in retail. Discussion paper FOR 3/18, Norwegian School of Economics, Norway. [http://hdl.handle.net/11250/2490886](http://hdl.handle.net/11250/2490886)
|
|
160
161
|
- Olden A, Moen J (2022) The triple difference estimator. *The Econometrics Journal* 25(3): 531-553. [10.1093/ectj/utac010](https://doi.org/10.1093/ectj/utac010)
|
|
161
162
|
- Strassmann A, Çolak Y, Serra-Burriel M, Nordestgaard BG, Turk A, Afzal S, Puhan MA (2023) Nationwide indoor smoking ban and impact on smoking behaviour and lung function: a two-population natural experiment. *Thorax* 78(2): 144-150. [10.1136/thoraxjnl-2021-218436](https://doi.org/10.1136/thoraxjnl-2021-218436)
|
|
@@ -171,9 +172,12 @@ See the /tests directory for usage examples of most of the included functions.
|
|
|
171
172
|
This software was developed without the use of AI-generated code. The Continue Agent 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).
|
|
172
173
|
|
|
173
174
|
|
|
174
|
-
## What's new (v2.5.
|
|
175
|
+
## What's new (v2.5.4)
|
|
175
176
|
|
|
176
|
-
- Extensions
|
|
177
|
-
- Added optional saving of plots in DiffModel plot methods
|
|
178
177
|
- Bugfixes
|
|
179
|
-
-
|
|
178
|
+
- Correction of the nonsensical skipping of the treatment group dummy in the case of two observation units in didanalysis.did_analysis()
|
|
179
|
+
- Automatical drop of duplicates in results dictionaries in didanalysis_helper.extract_model_results()
|
|
180
|
+
- Inefficient successive data insert in diddata.DiffData.add_synthetic() replaced by efficient concatenating
|
|
181
|
+
- Other
|
|
182
|
+
- Test script extended by another example
|
|
183
|
+
- Cleaned dependencies
|
|
@@ -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-
|
|
7
|
+
# Version: 1.0.25
|
|
8
|
+
# Last update: 2026-08-04 18: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.4"
|
|
16
16
|
|
|
17
17
|
VERBOSE = False
|
|
18
18
|
|
|
@@ -26,6 +26,8 @@ FIXED_EFFECTS_THRESHOLD = 3
|
|
|
26
26
|
|
|
27
27
|
AUTO_SKIP_CONSTANT_COLUMNS = True
|
|
28
28
|
|
|
29
|
+
REMOVE_DUPLICATES_FROM_RESULTS_DICT = True
|
|
30
|
+
|
|
29
31
|
ACCEPT_CONTINUOUS_TREATMENTS = True
|
|
30
32
|
|
|
31
33
|
# Description texts:
|
|
@@ -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.5
|
|
8
|
+
# Last update: 2026-08-03 20:25
|
|
9
9
|
# Copyright (c) 2024-2026 Thomas Wieland
|
|
10
10
|
#-----------------------------------------------------------------------
|
|
11
11
|
|
|
@@ -2512,7 +2512,7 @@ def did_analysis(
|
|
|
2512
2512
|
unique_units = treatment_diagnostics_results[3]
|
|
2513
2513
|
unique_time_points = treatment_diagnostics_results[4]
|
|
2514
2514
|
|
|
2515
|
-
if no_treatments > 1:
|
|
2515
|
+
if no_treatments > 1 and unique_units >= config.FIXED_EFFECTS_THRESHOLD:
|
|
2516
2516
|
|
|
2517
2517
|
intercept = False
|
|
2518
2518
|
TG_col = []
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
# Author: Thomas Wieland
|
|
5
5
|
# ORCID: 0000-0001-5168-9846
|
|
6
6
|
# mail: geowieland@googlemail.com
|
|
7
|
-
# Version: 1.2.
|
|
8
|
-
# Last update: 2026-
|
|
7
|
+
# Version: 1.2.4
|
|
8
|
+
# Last update: 2026-08-04 17:35
|
|
9
9
|
# Copyright (c) 2025-2026 Thomas Wieland
|
|
10
10
|
#-----------------------------------------------------------------------
|
|
11
11
|
|
|
@@ -25,7 +25,7 @@ def create_fixed_effects(
|
|
|
25
25
|
type: str = "unit",
|
|
26
26
|
drop_first: bool = False,
|
|
27
27
|
verbose: bool = config.VERBOSE
|
|
28
|
-
):
|
|
28
|
+
) -> list:
|
|
29
29
|
|
|
30
30
|
"""
|
|
31
31
|
Create dummy variables for fixed effects and attach them to the data.
|
|
@@ -118,7 +118,7 @@ def demean_variables(
|
|
|
118
118
|
max_iter=10,
|
|
119
119
|
tol=1e-8,
|
|
120
120
|
verbose: bool = config.VERBOSE
|
|
121
|
-
):
|
|
121
|
+
) -> tuple:
|
|
122
122
|
|
|
123
123
|
"""
|
|
124
124
|
Demean numeric variables by removing unit and/or time means.
|
|
@@ -399,7 +399,7 @@ def create_specific_time_trends(
|
|
|
399
399
|
FE_vars: list,
|
|
400
400
|
type: str = "ITT",
|
|
401
401
|
verbose: bool = config.VERBOSE
|
|
402
|
-
):
|
|
402
|
+
) -> list:
|
|
403
403
|
|
|
404
404
|
"""
|
|
405
405
|
Create unit-specific time trend variables (interactions with a time counter).
|
|
@@ -482,7 +482,7 @@ def create_specific_treatment_effects(
|
|
|
482
482
|
FE_vars: list,
|
|
483
483
|
type: str = "ITE",
|
|
484
484
|
verbose: bool = config.VERBOSE
|
|
485
|
-
):
|
|
485
|
+
) -> list:
|
|
486
486
|
|
|
487
487
|
"""
|
|
488
488
|
Create unit-specific treatment effect interaction variables.
|
|
@@ -566,7 +566,7 @@ def create_spillover(
|
|
|
566
566
|
spillover_treatment: list = None,
|
|
567
567
|
spillover_units: list = None,
|
|
568
568
|
verbose: bool = config.VERBOSE
|
|
569
|
-
):
|
|
569
|
+
) -> list:
|
|
570
570
|
|
|
571
571
|
"""
|
|
572
572
|
Create spillover indicator variables for given treatments and units.
|
|
@@ -667,7 +667,7 @@ def create_interactions(
|
|
|
667
667
|
data: pd.DataFrame,
|
|
668
668
|
interactions: dict = None,
|
|
669
669
|
verbose: bool = config.VERBOSE
|
|
670
|
-
):
|
|
670
|
+
) -> list:
|
|
671
671
|
|
|
672
672
|
"""
|
|
673
673
|
Create interaction variables by multiplying specified treatment columns.
|
|
@@ -787,7 +787,7 @@ def data_diagnostics(
|
|
|
787
787
|
drop_missing: bool = True,
|
|
788
788
|
missing_replace_by_zero: bool = False,
|
|
789
789
|
verbose: bool = config.VERBOSE
|
|
790
|
-
):
|
|
790
|
+
) -> dict:
|
|
791
791
|
|
|
792
792
|
"""
|
|
793
793
|
Run diagnostics on model data: missingness, balance and basic stats.
|
|
@@ -906,7 +906,7 @@ def treatment_diagnostics(
|
|
|
906
906
|
pre_post: bool = False,
|
|
907
907
|
confint_alpha = 0.05,
|
|
908
908
|
verbose: bool = config.VERBOSE
|
|
909
|
-
):
|
|
909
|
+
) -> list:
|
|
910
910
|
|
|
911
911
|
"""
|
|
912
912
|
Compute diagnostics for each treatment: simultaneity, parallel trends,
|
|
@@ -1057,7 +1057,7 @@ def ols_fit(
|
|
|
1057
1057
|
confint_alpha = 0.05,
|
|
1058
1058
|
cluster_SE_by: str = None,
|
|
1059
1059
|
verbose: bool = config.VERBOSE
|
|
1060
|
-
):
|
|
1060
|
+
) -> list:
|
|
1061
1061
|
|
|
1062
1062
|
"""
|
|
1063
1063
|
Estimate an OLS model using a formula and return coefficients and predictions.
|
|
@@ -1130,7 +1130,7 @@ def ml_fit(
|
|
|
1130
1130
|
family=sm.families.Gaussian(),
|
|
1131
1131
|
link=sm.families.links.Identity(),
|
|
1132
1132
|
verbose: bool = config.VERBOSE
|
|
1133
|
-
):
|
|
1133
|
+
) -> list:
|
|
1134
1134
|
|
|
1135
1135
|
"""
|
|
1136
1136
|
Estimate a model by maximum likelihood (GLM) and return coefficients and predictions.
|
|
@@ -1217,7 +1217,7 @@ def extract_model_results(
|
|
|
1217
1217
|
BG_x_TT_col: list = None,
|
|
1218
1218
|
covariates: list = None,
|
|
1219
1219
|
verbose: bool = config.VERBOSE
|
|
1220
|
-
):
|
|
1220
|
+
) -> dict:
|
|
1221
1221
|
|
|
1222
1222
|
"""
|
|
1223
1223
|
Compile and format model coefficient results into a structured dict.
|
|
@@ -1311,6 +1311,9 @@ def extract_model_results(
|
|
|
1311
1311
|
"CI_lower": float(coef_conf_intervals.loc[treatment, 0]),
|
|
1312
1312
|
"CI_upper": float(coef_conf_intervals.loc[treatment, 1]),
|
|
1313
1313
|
}
|
|
1314
|
+
|
|
1315
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1316
|
+
ATE = remove_duplicates_from_dict(ATE)
|
|
1314
1317
|
|
|
1315
1318
|
model_results = {config.EFFECTS_TYPES["ATE"]["model_results_key"]: ATE}
|
|
1316
1319
|
|
|
@@ -1329,6 +1332,9 @@ def extract_model_results(
|
|
|
1329
1332
|
"CI_upper": float(coef_conf_intervals.loc[TG_, 1]),
|
|
1330
1333
|
}
|
|
1331
1334
|
|
|
1335
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1336
|
+
beta_1 = remove_duplicates_from_dict(beta_1)
|
|
1337
|
+
|
|
1332
1338
|
model_results[config.EFFECTS_TYPES["beta_1"]["model_results_key"]] = beta_1
|
|
1333
1339
|
|
|
1334
1340
|
if (len(TT_col) > 0) and (any(col in coefficients for col in TT_col)):
|
|
@@ -1346,6 +1352,9 @@ def extract_model_results(
|
|
|
1346
1352
|
"CI_upper": float(coef_conf_intervals.loc[TT_, 1]),
|
|
1347
1353
|
}
|
|
1348
1354
|
|
|
1355
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1356
|
+
delta_0 = remove_duplicates_from_dict(delta_0)
|
|
1357
|
+
|
|
1349
1358
|
model_results[config.EFFECTS_TYPES["delta_0"]["model_results_key"]] = delta_0
|
|
1350
1359
|
|
|
1351
1360
|
if "Intercept" in coefficients:
|
|
@@ -1377,6 +1386,9 @@ def extract_model_results(
|
|
|
1377
1386
|
"CI_upper": float(coef_conf_intervals.loc[AATE_, 1]),
|
|
1378
1387
|
}
|
|
1379
1388
|
|
|
1389
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1390
|
+
AATE = remove_duplicates_from_dict(AATE)
|
|
1391
|
+
|
|
1380
1392
|
model_results[config.EFFECTS_TYPES["AATE"]["model_results_key"]] = AATE
|
|
1381
1393
|
|
|
1382
1394
|
if (len(ATT_col) > 0) and (any(col in coefficients for col in ATT_col)):
|
|
@@ -1394,6 +1406,9 @@ def extract_model_results(
|
|
|
1394
1406
|
"CI_upper": float(coef_conf_intervals.loc[ATT_, 1]),
|
|
1395
1407
|
}
|
|
1396
1408
|
|
|
1409
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1410
|
+
ATT = remove_duplicates_from_dict(ATT)
|
|
1411
|
+
|
|
1397
1412
|
model_results[config.EFFECTS_TYPES["ATT"]["model_results_key"]] = ATT
|
|
1398
1413
|
|
|
1399
1414
|
if (len(spillover_vars) > 0) and (any(col in coefficients for col in spillover_vars)):
|
|
@@ -1411,6 +1426,9 @@ def extract_model_results(
|
|
|
1411
1426
|
"CI_upper": float(coef_conf_intervals.loc[spillover_var, 1]),
|
|
1412
1427
|
}
|
|
1413
1428
|
|
|
1429
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1430
|
+
spillover_coef = remove_duplicates_from_dict(spillover_coef)
|
|
1431
|
+
|
|
1414
1432
|
model_results[config.EFFECTS_TYPES["spillover"]["model_results_key"]] = spillover_coef
|
|
1415
1433
|
|
|
1416
1434
|
fixed_effects = [
|
|
@@ -1435,6 +1453,9 @@ def extract_model_results(
|
|
|
1435
1453
|
"Coefficient_type": config.EFFECTS_TYPES["FE"]["types"][0]["description"]
|
|
1436
1454
|
}
|
|
1437
1455
|
|
|
1456
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1457
|
+
FE_unit_coef = remove_duplicates_from_dict(FE_unit_coef)
|
|
1458
|
+
|
|
1438
1459
|
fixed_effects[0] = {config.EFFECTS_TYPES["FE"]["types"][0]["model_results_key"]: FE_unit_coef}
|
|
1439
1460
|
|
|
1440
1461
|
if (len(FE_time_vars) > 0) and (any(col in coefficients for col in FE_time_vars)):
|
|
@@ -1452,7 +1473,10 @@ def extract_model_results(
|
|
|
1452
1473
|
"CI_upper": float(coef_conf_intervals.loc[time_dummy, 1]),
|
|
1453
1474
|
"Coefficient_type": config.EFFECTS_TYPES["FE"]["types"][1]["description"]
|
|
1454
1475
|
}
|
|
1455
|
-
|
|
1476
|
+
|
|
1477
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1478
|
+
FE_time_coef = remove_duplicates_from_dict(FE_time_coef)
|
|
1479
|
+
|
|
1456
1480
|
fixed_effects[1] = {config.EFFECTS_TYPES["FE"]["types"][1]["model_results_key"]: FE_time_coef}
|
|
1457
1481
|
|
|
1458
1482
|
if (len(FE_group_vars) > 0) and (any(col in coefficients for col in FE_group_vars)):
|
|
@@ -1470,7 +1494,10 @@ def extract_model_results(
|
|
|
1470
1494
|
"CI_upper": float(coef_conf_intervals.loc[group_dummy, 1]),
|
|
1471
1495
|
"Coefficient_type": config.EFFECTS_TYPES["FE"]["types"][2]["description"]
|
|
1472
1496
|
}
|
|
1473
|
-
|
|
1497
|
+
|
|
1498
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1499
|
+
FE_group_coef = remove_duplicates_from_dict(FE_group_coef)
|
|
1500
|
+
|
|
1474
1501
|
fixed_effects[2] = {config.EFFECTS_TYPES["FE"]["types"][2]["model_results_key"]: FE_group_coef}
|
|
1475
1502
|
|
|
1476
1503
|
model_results[config.EFFECTS_TYPES["FE"]["model_results_key"]] = fixed_effects
|
|
@@ -1491,6 +1518,9 @@ def extract_model_results(
|
|
|
1491
1518
|
"CI_upper": float(coef_conf_intervals.loc[ITT_var, 1]),
|
|
1492
1519
|
}
|
|
1493
1520
|
|
|
1521
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1522
|
+
ITT_coef = remove_duplicates_from_dict(ITT_coef)
|
|
1523
|
+
|
|
1494
1524
|
model_results["individual_time_trends"] = ITT_coef
|
|
1495
1525
|
|
|
1496
1526
|
if (len(ITE_vars) > 0) and (any(col in coefficients for col in ITE_vars)):
|
|
@@ -1516,6 +1546,9 @@ def extract_model_results(
|
|
|
1516
1546
|
"CI_upper": float(coef_conf_intervals.loc[ITE_var, 1]),
|
|
1517
1547
|
}
|
|
1518
1548
|
|
|
1549
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1550
|
+
ITE_coef = remove_duplicates_from_dict(ITE_coef)
|
|
1551
|
+
|
|
1519
1552
|
model_results["individual_treatment_effects"] = ITE_coef
|
|
1520
1553
|
|
|
1521
1554
|
if (len(GTT_vars) > 0) and (any(col in coefficients for col in GTT_vars)):
|
|
@@ -1534,6 +1567,9 @@ def extract_model_results(
|
|
|
1534
1567
|
"CI_upper": float(coef_conf_intervals.loc[GTT_var, 1]),
|
|
1535
1568
|
}
|
|
1536
1569
|
|
|
1570
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1571
|
+
GTT_coef = remove_duplicates_from_dict(GTT_coef)
|
|
1572
|
+
|
|
1537
1573
|
model_results["group_time_trends"] = GTT_coef
|
|
1538
1574
|
|
|
1539
1575
|
if (len(GTE_vars) > 0) and (any(col in coefficients for col in GTE_vars)):
|
|
@@ -1558,6 +1594,9 @@ def extract_model_results(
|
|
|
1558
1594
|
"CI_upper": float(coef_conf_intervals.loc[GTE_var, 1]),
|
|
1559
1595
|
}
|
|
1560
1596
|
|
|
1597
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1598
|
+
GTE_coef = remove_duplicates_from_dict(GTE_coef)
|
|
1599
|
+
|
|
1561
1600
|
model_results["group_treatment_effects"] = GTE_coef
|
|
1562
1601
|
|
|
1563
1602
|
if (len(covariates) > 0) and (any(col in coefficients for col in covariates)):
|
|
@@ -1577,6 +1616,9 @@ def extract_model_results(
|
|
|
1577
1616
|
"CI_lower": float(coef_conf_intervals.loc[covariate, 0]),
|
|
1578
1617
|
"CI_upper": float(coef_conf_intervals.loc[covariate, 1]),
|
|
1579
1618
|
}
|
|
1619
|
+
|
|
1620
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1621
|
+
covariates_effects = remove_duplicates_from_dict(covariates_effects)
|
|
1580
1622
|
|
|
1581
1623
|
model_results["covariates_effects"] = covariates_effects
|
|
1582
1624
|
|
|
@@ -1595,6 +1637,9 @@ def extract_model_results(
|
|
|
1595
1637
|
"CI_upper": float(coef_conf_intervals.loc[TG_x_BG_x_TT_, 1]),
|
|
1596
1638
|
}
|
|
1597
1639
|
|
|
1640
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1641
|
+
TDATE = remove_duplicates_from_dict(TDATE)
|
|
1642
|
+
|
|
1598
1643
|
model_results = {config.EFFECTS_TYPES_DDD["TDATE"]["model_results_key"]: TDATE}
|
|
1599
1644
|
|
|
1600
1645
|
if (len(BG_col) > 0) and (any(col in coefficients for col in BG_col)):
|
|
@@ -1612,6 +1657,9 @@ def extract_model_results(
|
|
|
1612
1657
|
"CI_upper": float(coef_conf_intervals.loc[BG_, 1]),
|
|
1613
1658
|
}
|
|
1614
1659
|
|
|
1660
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1661
|
+
BG = remove_duplicates_from_dict(BG)
|
|
1662
|
+
|
|
1615
1663
|
model_results[config.EFFECTS_TYPES_DDD["beta_2"]["model_results_key"]] = BG
|
|
1616
1664
|
|
|
1617
1665
|
if (len(TG_x_BG_col) > 0) and (any(col in coefficients for col in TG_x_BG_col)):
|
|
@@ -1627,7 +1675,10 @@ def extract_model_results(
|
|
|
1627
1675
|
"p": float(coef_p[TG_x_BG_]),
|
|
1628
1676
|
"CI_lower": float(coef_conf_intervals.loc[TG_x_BG_, 0]),
|
|
1629
1677
|
"CI_upper": float(coef_conf_intervals.loc[TG_x_BG_, 1]),
|
|
1630
|
-
}
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1681
|
+
TG_x_BG = remove_duplicates_from_dict(TG_x_BG)
|
|
1631
1682
|
|
|
1632
1683
|
model_results[config.EFFECTS_TYPES_DDD["beta_4"]["model_results_key"]] = TG_x_BG
|
|
1633
1684
|
|
|
@@ -1646,6 +1697,9 @@ def extract_model_results(
|
|
|
1646
1697
|
"CI_upper": float(coef_conf_intervals.loc[BG_x_TT_, 1]),
|
|
1647
1698
|
}
|
|
1648
1699
|
|
|
1700
|
+
if config.REMOVE_DUPLICATES_FROM_RESULTS_DICT:
|
|
1701
|
+
BG_x_TT = remove_duplicates_from_dict(BG_x_TT)
|
|
1702
|
+
|
|
1649
1703
|
model_results[config.EFFECTS_TYPES_DDD["beta_6"]["model_results_key"]] = BG_x_TT
|
|
1650
1704
|
|
|
1651
1705
|
if verbose:
|
|
@@ -1658,7 +1712,7 @@ def fit_metrics(
|
|
|
1658
1712
|
outcome_col,
|
|
1659
1713
|
model_predictions,
|
|
1660
1714
|
indep_vars_no: int = None
|
|
1661
|
-
):
|
|
1715
|
+
) -> list:
|
|
1662
1716
|
|
|
1663
1717
|
"""
|
|
1664
1718
|
Compute fit metrics for a model given data and predictions.
|
|
@@ -1704,7 +1758,7 @@ def fit_metrics(
|
|
|
1704
1758
|
|
|
1705
1759
|
return fit_metrics_result
|
|
1706
1760
|
|
|
1707
|
-
def create_timestamp(function):
|
|
1761
|
+
def create_timestamp(function) -> dict:
|
|
1708
1762
|
|
|
1709
1763
|
"""
|
|
1710
1764
|
Create a standard timestamp dictionary for logging or metadata.
|
|
@@ -1743,4 +1797,35 @@ def create_timestamp(function):
|
|
|
1743
1797
|
"datetime": now.strftime("%Y-%m-%d %H-%M-%S")
|
|
1744
1798
|
}
|
|
1745
1799
|
|
|
1746
|
-
return timestamp_dict
|
|
1800
|
+
return timestamp_dict
|
|
1801
|
+
|
|
1802
|
+
def remove_duplicates_from_dict(
|
|
1803
|
+
any_dict: dict
|
|
1804
|
+
) -> dict:
|
|
1805
|
+
|
|
1806
|
+
"""
|
|
1807
|
+
Remove duplicate values from a dictionary.
|
|
1808
|
+
|
|
1809
|
+
Parameters
|
|
1810
|
+
----------
|
|
1811
|
+
any_dict : dict
|
|
1812
|
+
Dictionary from which to remove duplicates.
|
|
1813
|
+
|
|
1814
|
+
Returns
|
|
1815
|
+
-------
|
|
1816
|
+
dict
|
|
1817
|
+
Dictionary with unique values.
|
|
1818
|
+
"""
|
|
1819
|
+
|
|
1820
|
+
any_dict_unique = {}
|
|
1821
|
+
seen = set()
|
|
1822
|
+
|
|
1823
|
+
for key, value in any_dict.items():
|
|
1824
|
+
|
|
1825
|
+
identifier = tuple(sorted(value.items()))
|
|
1826
|
+
|
|
1827
|
+
if identifier not in seen:
|
|
1828
|
+
seen.add(identifier)
|
|
1829
|
+
any_dict_unique[key] = value
|
|
1830
|
+
|
|
1831
|
+
return any_dict_unique
|
|
@@ -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.3.
|
|
8
|
-
# Last update: 2026-
|
|
7
|
+
# Version: 2.3.3
|
|
8
|
+
# Last update: 2026-08-04 17:51
|
|
9
9
|
# Copyright (c) 2024-2026 Thomas Wieland
|
|
10
10
|
#-----------------------------------------------------------------------
|
|
11
11
|
|
|
@@ -1986,10 +1986,11 @@ class DiffData:
|
|
|
1986
1986
|
}
|
|
1987
1987
|
)
|
|
1988
1988
|
|
|
1989
|
+
columns = {}
|
|
1989
1990
|
units_not_included = []
|
|
1991
|
+
valid_units = []
|
|
1990
1992
|
|
|
1991
1993
|
for unit in units:
|
|
1992
|
-
|
|
1993
1994
|
did_modeldata_unit = did_modeldata.loc[
|
|
1994
1995
|
(did_modeldata[config.UNIT_COL].astype(str) == str(unit))
|
|
1995
1996
|
& (did_modeldata[outcome_col].notna()),
|
|
@@ -1998,9 +1999,19 @@ class DiffData:
|
|
|
1998
1999
|
|
|
1999
2000
|
if len(did_modeldata_unit) != len(outcome_matrix):
|
|
2000
2001
|
units_not_included.append(unit)
|
|
2001
|
-
units.remove(unit)
|
|
2002
2002
|
else:
|
|
2003
|
-
|
|
2003
|
+
valid_units.append(unit)
|
|
2004
|
+
columns[tools.clean_column_name(unit)] = did_modeldata_unit.reset_index(drop=True)
|
|
2005
|
+
|
|
2006
|
+
outcome_matrix = pd.concat(
|
|
2007
|
+
[
|
|
2008
|
+
outcome_matrix,
|
|
2009
|
+
pd.DataFrame(columns)
|
|
2010
|
+
],
|
|
2011
|
+
axis=1
|
|
2012
|
+
)
|
|
2013
|
+
|
|
2014
|
+
units = valid_units
|
|
2004
2015
|
|
|
2005
2016
|
if len(units_not_included) > 0:
|
|
2006
2017
|
print(f"WARNING: {len(units_not_included)} analysis units were not included due to NaN data.")
|
|
@@ -4,16 +4,18 @@
|
|
|
4
4
|
# Author: Thomas Wieland
|
|
5
5
|
# ORCID: 0000-0001-5168-9846
|
|
6
6
|
# mail: geowieland@googlemail.com
|
|
7
|
-
# Version: 2.
|
|
8
|
-
# Last update: 2026-
|
|
7
|
+
# Version: 2.2.0
|
|
8
|
+
# Last update: 2026-08-04 18:42
|
|
9
9
|
# Copyright (c) 2025-2026 Thomas Wieland
|
|
10
10
|
#-----------------------------------------------------------------------
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
"""
|
|
13
14
|
Please note that this tests script is a collection of examples for the use of most functions of the diffindiff package.
|
|
14
15
|
Although the data is real (i.e., non-simulated), no results relevant to the real world can be derived from it.
|
|
15
16
|
"""
|
|
16
17
|
|
|
18
|
+
from pathlib import Path
|
|
17
19
|
import pandas as pd
|
|
18
20
|
from diffindiff.didanalysis import did_analysis
|
|
19
21
|
from diffindiff.diddata import create_groups, create_treatment, merge_data, create_data
|
|
@@ -22,7 +24,11 @@ from diffindiff.diddata import create_groups, create_treatment, merge_data, crea
|
|
|
22
24
|
# Example 1: Effect of a curfew in German counties in the first
|
|
23
25
|
# wave of the COVID-19 pandemic (DiD pre-post analysis)
|
|
24
26
|
|
|
25
|
-
curfew_DE=pd.read_csv(
|
|
27
|
+
curfew_DE = pd.read_csv(
|
|
28
|
+
Path(__file__).parent / "data" / "curfew_DE.csv",
|
|
29
|
+
sep=";",
|
|
30
|
+
decimal=","
|
|
31
|
+
)
|
|
26
32
|
# Dataset with daily and cumulative SARS-CoV-2 infections of German counties
|
|
27
33
|
# Data source: Wieland (2020) https://doi.org/10.18335/region.v7i2.324
|
|
28
34
|
|
|
@@ -193,7 +199,12 @@ curfew_model_prepost_AT.plot(
|
|
|
193
199
|
# Plot DiD pre vs. post results
|
|
194
200
|
# with user-determined style
|
|
195
201
|
|
|
196
|
-
counties_DE=pd.read_csv(
|
|
202
|
+
counties_DE = pd.read_csv(
|
|
203
|
+
Path(__file__).parent / "data" / "counties_DE.csv",
|
|
204
|
+
sep=";",
|
|
205
|
+
decimal=",",
|
|
206
|
+
encoding='latin1'
|
|
207
|
+
)
|
|
197
208
|
# Dataset with German county data
|
|
198
209
|
|
|
199
210
|
curfew_data_prepost_withcov = curfew_data_prepost.add_covariates(
|
|
@@ -405,7 +416,9 @@ curfew_model_extended.plot(
|
|
|
405
416
|
# Example 3: Nighttime curfew and other NPI in Hesse
|
|
406
417
|
# (Staggered adoption)
|
|
407
418
|
|
|
408
|
-
Corona_Hesse=pd.read_excel(
|
|
419
|
+
Corona_Hesse = pd.read_excel(
|
|
420
|
+
Path(__file__).parent / "data" / "Corona_Hesse.xlsx",
|
|
421
|
+
)
|
|
409
422
|
# Test data effective reproduction number and Corona NPI Hesse
|
|
410
423
|
# Data source: Wieland (2025) https://doi.org/10.1007/s10389-024-02218-x
|
|
411
424
|
|
|
@@ -530,4 +543,75 @@ Hesse_model6=did_analysis(
|
|
|
530
543
|
# Model with two interventions and one interaction of the two treatments
|
|
531
544
|
|
|
532
545
|
Hesse_model6.summary()
|
|
533
|
-
# Model summary
|
|
546
|
+
# Model summary
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
# Example 4: Mandatory face masks in Jena (Germany) during the first Corona wave
|
|
550
|
+
# data: German counties during the first Corona wave
|
|
551
|
+
|
|
552
|
+
facemasks_data=create_data(
|
|
553
|
+
outcome_data=curfew_DE,
|
|
554
|
+
unit_id_col="county",
|
|
555
|
+
time_col="infection_date",
|
|
556
|
+
outcome_col="infections_cum_per100000",
|
|
557
|
+
treatment_group=
|
|
558
|
+
curfew_DE.loc[curfew_DE["REG_NAME"] == "Jena"]["county"],
|
|
559
|
+
control_group=
|
|
560
|
+
curfew_DE.loc[curfew_DE["REG_NAME"] != "Jena"]["county"],
|
|
561
|
+
treatment_name="Mandatory face masks",
|
|
562
|
+
study_period=["2020-03-01", "2020-04-26"],
|
|
563
|
+
treatment_period=["2020-04-06", "2020-04-26"],
|
|
564
|
+
freq="D"
|
|
565
|
+
)
|
|
566
|
+
# Creating DiD dataset by defining groups and treatment time at once
|
|
567
|
+
# Treatment mandatory face masks in Jena beginning April 6, 2020 (first German city with mandatory face masks)
|
|
568
|
+
|
|
569
|
+
facemasks_data.summary()
|
|
570
|
+
# Summary of created treatment data
|
|
571
|
+
|
|
572
|
+
facemasks_data_analysis = facemasks_data.analysis(
|
|
573
|
+
FE_unit=True,
|
|
574
|
+
FE_time=True,
|
|
575
|
+
intercept=False,
|
|
576
|
+
verbose=True
|
|
577
|
+
)
|
|
578
|
+
# DiD analysis as two-way fixed effects model
|
|
579
|
+
|
|
580
|
+
facemasks_data_analysis.summary()
|
|
581
|
+
# Summary of model results
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
facemasks_data_analysis_demean = facemasks_data.analysis(
|
|
585
|
+
FE_unit=True,
|
|
586
|
+
FE_time=True,
|
|
587
|
+
intercept=False,
|
|
588
|
+
demean=True,
|
|
589
|
+
verbose=True
|
|
590
|
+
)
|
|
591
|
+
# DiD analysis as model with demeaened variables instead of fixed effects
|
|
592
|
+
|
|
593
|
+
facemasks_data_analysis_demean.summary()
|
|
594
|
+
# Summary of model results
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
facemasks_data_synth = facemasks_data.add_synthetic(process_unit="Jena")
|
|
598
|
+
# Add synthetic control for Jena
|
|
599
|
+
|
|
600
|
+
facemasks_data_synth.summary()
|
|
601
|
+
# Summary of synthetic DiD data
|
|
602
|
+
|
|
603
|
+
print(facemasks_data_synth.get_synthetic_control_weightings())
|
|
604
|
+
# Weights
|
|
605
|
+
|
|
606
|
+
print(facemasks_data_synth.data[0].head)
|
|
607
|
+
# Treatment unit with counterfactual (synthetic) control unit
|
|
608
|
+
|
|
609
|
+
facemasks_data_synth_analysis = facemasks_data_synth.analysis(
|
|
610
|
+
FE_time=True,
|
|
611
|
+
log_outcome=True,
|
|
612
|
+
verbose=True
|
|
613
|
+
)
|
|
614
|
+
# Synthetic DiD analysis
|
|
615
|
+
|
|
616
|
+
facemasks_data_synth_analysis.summary()
|
|
617
|
+
# Summary of model results
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: diffindiff
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.4
|
|
4
4
|
Summary: diffindiff: Python library for convenient Difference-in-Differences analyses
|
|
5
5
|
Author: Thomas Wieland
|
|
6
6
|
Author-email: geowieland@googlemail.com
|
|
@@ -29,7 +29,7 @@ A case study that utilizes the diffindiff library is available on [arXiv](https:
|
|
|
29
29
|
|
|
30
30
|
If you use this software, please cite:
|
|
31
31
|
|
|
32
|
-
Wieland, T. (2026). diffindiff: A Python library for convenient difference-in-differences analyses (Version 2.5.
|
|
32
|
+
Wieland, T. (2026). diffindiff: A Python library for convenient difference-in-differences analyses (Version 2.5.4) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.18656820
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
## Installation
|
|
@@ -164,6 +164,7 @@ See the /tests directory for usage examples of most of the included functions.
|
|
|
164
164
|
- Goldfarb A, Tucker C, Wang Y (2022) Conducting Research in Marketing with Quasi-Experiments. *Journal of Marketing* 86(3): 1-19. [10.1177/00222429221082977](https://doi.org/10.1177/00222429221082977)
|
|
165
165
|
- Isporhing IE, Lipfert M, Pestel N (2021) Does re-opening schools contribute to the spread of SARS-CoV-2? Evidence from staggered summer breaks in Germany. *Journal of Public Economics* 198: 104426. [10.1016/j.jpubeco.2021.104426](https://doi.org/10.1016/j.jpubeco.2021.104426)
|
|
166
166
|
- Li KT, Luo L, Pattabhiramaiah A (2024) Causal Inference with Quasi-Experimental Data. *IMPACT at JMR* November 13, 2024. [AMA](https://www.ama.org/marketing-news/causal-inference-with-quasi-experimental-data/)
|
|
167
|
+
- Mitze T, Kosfeld R, Rode J, Wälde K (2020) Face masks considerably reduce COVID-19 cases in Germany. *Proceedings of the National Academy of Sciences of the United States of America* 117(51): 32293-32301. [10.1073/pnas.2015954117](https://doi.org/10.1073/pnas.2015954117)
|
|
167
168
|
- Olden A (2018) What do you buy when no one's watching? The effect of self-service checkouts on the composition of sales in retail. Discussion paper FOR 3/18, Norwegian School of Economics, Norway. [http://hdl.handle.net/11250/2490886](http://hdl.handle.net/11250/2490886)
|
|
168
169
|
- Olden A, Moen J (2022) The triple difference estimator. *The Econometrics Journal* 25(3): 531-553. [10.1093/ectj/utac010](https://doi.org/10.1093/ectj/utac010)
|
|
169
170
|
- Strassmann A, Çolak Y, Serra-Burriel M, Nordestgaard BG, Turk A, Afzal S, Puhan MA (2023) Nationwide indoor smoking ban and impact on smoking behaviour and lung function: a two-population natural experiment. *Thorax* 78(2): 144-150. [10.1136/thoraxjnl-2021-218436](https://doi.org/10.1136/thoraxjnl-2021-218436)
|
|
@@ -179,9 +180,12 @@ See the /tests directory for usage examples of most of the included functions.
|
|
|
179
180
|
This software was developed without the use of AI-generated code. The Continue Agent 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).
|
|
180
181
|
|
|
181
182
|
|
|
182
|
-
## What's new (v2.5.
|
|
183
|
+
## What's new (v2.5.4)
|
|
183
184
|
|
|
184
|
-
- Extensions
|
|
185
|
-
- Added optional saving of plots in DiffModel plot methods
|
|
186
185
|
- Bugfixes
|
|
187
|
-
-
|
|
186
|
+
- Correction of the nonsensical skipping of the treatment group dummy in the case of two observation units in didanalysis.did_analysis()
|
|
187
|
+
- Automatical drop of duplicates in results dictionaries in didanalysis_helper.extract_model_results()
|
|
188
|
+
- Inefficient successive data insert in diddata.DiffData.add_synthetic() replaced by efficient concatenating
|
|
189
|
+
- Other
|
|
190
|
+
- Test script extended by another example
|
|
191
|
+
- Cleaned dependencies
|
|
@@ -7,7 +7,7 @@ def read_README():
|
|
|
7
7
|
|
|
8
8
|
setup(
|
|
9
9
|
name='diffindiff',
|
|
10
|
-
version='2.5.
|
|
10
|
+
version='2.5.4',
|
|
11
11
|
description='diffindiff: Python library for convenient Difference-in-Differences analyses',
|
|
12
12
|
packages=find_packages(include=["diffindiff", "diffindiff.tests"]),
|
|
13
13
|
include_package_data=True,
|
|
@@ -20,7 +20,6 @@ setup(
|
|
|
20
20
|
'diffindiff': ['tests/data/*'],
|
|
21
21
|
},
|
|
22
22
|
install_requires=[
|
|
23
|
-
'geopandas',
|
|
24
23
|
'pandas',
|
|
25
24
|
'numpy',
|
|
26
25
|
'statsmodels>=0.14.5',
|
|
@@ -28,9 +27,6 @@ setup(
|
|
|
28
27
|
'scikit-learn',
|
|
29
28
|
'xgboost',
|
|
30
29
|
'lightgbm',
|
|
31
|
-
'shapely',
|
|
32
|
-
'requests<3.0',
|
|
33
|
-
'contextily',
|
|
34
30
|
'openpyxl',
|
|
35
31
|
'matplotlib',
|
|
36
32
|
'patsy',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|