diffindiff 2.5.0__tar.gz → 2.5.1__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.0 → diffindiff-2.5.1}/PKG-INFO +6 -13
- {diffindiff-2.5.0 → diffindiff-2.5.1}/README.md +5 -12
- {diffindiff-2.5.0 → diffindiff-2.5.1}/diffindiff/config.py +4 -3
- {diffindiff-2.5.0 → diffindiff-2.5.1}/diffindiff/didanalysis.py +88 -12
- {diffindiff-2.5.0 → diffindiff-2.5.1}/diffindiff/diddata.py +58 -13
- {diffindiff-2.5.0 → diffindiff-2.5.1}/diffindiff/tests/tests_diffindiff.py +59 -3
- {diffindiff-2.5.0 → diffindiff-2.5.1}/diffindiff.egg-info/PKG-INFO +6 -13
- {diffindiff-2.5.0 → diffindiff-2.5.1}/setup.py +1 -1
- {diffindiff-2.5.0 → diffindiff-2.5.1}/MANIFEST.in +0 -0
- {diffindiff-2.5.0 → diffindiff-2.5.1}/diffindiff/__init__.py +0 -0
- {diffindiff-2.5.0 → diffindiff-2.5.1}/diffindiff/didanalysis_helper.py +0 -0
- {diffindiff-2.5.0 → diffindiff-2.5.1}/diffindiff/didtools.py +0 -0
- {diffindiff-2.5.0 → diffindiff-2.5.1}/diffindiff/tests/__init__.py +0 -0
- {diffindiff-2.5.0 → diffindiff-2.5.1}/diffindiff/tests/data/Corona_Hesse.xlsx +0 -0
- {diffindiff-2.5.0 → diffindiff-2.5.1}/diffindiff/tests/data/counties_DE.csv +0 -0
- {diffindiff-2.5.0 → diffindiff-2.5.1}/diffindiff/tests/data/curfew_DE.csv +0 -0
- {diffindiff-2.5.0 → diffindiff-2.5.1}/diffindiff.egg-info/SOURCES.txt +0 -0
- {diffindiff-2.5.0 → diffindiff-2.5.1}/diffindiff.egg-info/dependency_links.txt +0 -0
- {diffindiff-2.5.0 → diffindiff-2.5.1}/diffindiff.egg-info/requires.txt +0 -0
- {diffindiff-2.5.0 → diffindiff-2.5.1}/diffindiff.egg-info/top_level.txt +0 -0
- {diffindiff-2.5.0 → diffindiff-2.5.1}/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.1
|
|
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 research note featuring a case study that utilizes the diffindiff library is a
|
|
|
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.1) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.18656820
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
## Installation
|
|
@@ -177,16 +177,9 @@ See the /tests directory for usage examples of most of the included functions.
|
|
|
177
177
|
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).
|
|
178
178
|
|
|
179
179
|
|
|
180
|
-
## What's new (v2.5.
|
|
180
|
+
## What's new (v2.5.1)
|
|
181
181
|
|
|
182
|
-
- General
|
|
183
|
-
- Update of dependencies to be compatible with Python 3.13 (still works with Python 3.11) while avoiding incompatibility conflict of statsmodels and scipy
|
|
184
|
-
- Internal changes in some functions to be compatible with Python 3.13 (still works with Python 3.11)
|
|
185
|
-
- Extensions
|
|
186
|
-
- Creating synthetic control units with DiffData.add_synthetic() and conducting a Synthetic DiD analysis
|
|
187
|
-
- didanalysis_helper.treatment_diagnostics() now additionally checks unique number of analysis units and time points
|
|
188
|
-
- didanalysis_helper.data_diagnostics() now additionally checks whether covariates are constant; didanalysis.did_analysis() and .ddd_analysis() automatically skip such variables from the model analysis
|
|
189
182
|
- Bugfixes
|
|
190
|
-
- DiffData.
|
|
191
|
-
-
|
|
192
|
-
-
|
|
183
|
+
- In DiffData.create_treatment() and DiffData.create_data(), pre-post designs with after-treatment period (follow-up) now work correctly
|
|
184
|
+
- DiffModel.plot() nows supports pre-post designs with after-treatment period
|
|
185
|
+
- Some small stabilizations
|
|
@@ -21,7 +21,7 @@ A research note featuring a case study that utilizes the diffindiff library is a
|
|
|
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.1) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.18656820
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
## Installation
|
|
@@ -169,16 +169,9 @@ See the /tests directory for usage examples of most of the included functions.
|
|
|
169
169
|
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).
|
|
170
170
|
|
|
171
171
|
|
|
172
|
-
## What's new (v2.5.
|
|
172
|
+
## What's new (v2.5.1)
|
|
173
173
|
|
|
174
|
-
- General
|
|
175
|
-
- Update of dependencies to be compatible with Python 3.13 (still works with Python 3.11) while avoiding incompatibility conflict of statsmodels and scipy
|
|
176
|
-
- Internal changes in some functions to be compatible with Python 3.13 (still works with Python 3.11)
|
|
177
|
-
- Extensions
|
|
178
|
-
- Creating synthetic control units with DiffData.add_synthetic() and conducting a Synthetic DiD analysis
|
|
179
|
-
- didanalysis_helper.treatment_diagnostics() now additionally checks unique number of analysis units and time points
|
|
180
|
-
- didanalysis_helper.data_diagnostics() now additionally checks whether covariates are constant; didanalysis.did_analysis() and .ddd_analysis() automatically skip such variables from the model analysis
|
|
181
174
|
- Bugfixes
|
|
182
|
-
- DiffData.
|
|
183
|
-
-
|
|
184
|
-
-
|
|
175
|
+
- In DiffData.create_treatment() and DiffData.create_data(), pre-post designs with after-treatment period (follow-up) now work correctly
|
|
176
|
+
- DiffModel.plot() nows supports pre-post designs with after-treatment period
|
|
177
|
+
- Some small stabilizations
|
|
@@ -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-07-
|
|
7
|
+
# Version: 1.0.22
|
|
8
|
+
# Last update: 2026-07-10 11:36
|
|
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.1"
|
|
16
16
|
|
|
17
17
|
VERBOSE = False
|
|
18
18
|
|
|
@@ -52,6 +52,7 @@ TREATMENT_PERIOD_DESCRIPTION = f"{TREATMENT_DESCRIPTION} period"
|
|
|
52
52
|
STUDY_PERIOD_DESCRIPTION = "Study period"
|
|
53
53
|
PREPOST_DESCRIPTION = "Pre-post"
|
|
54
54
|
AFTER_TREATMENT_PERIOD_DESCRIPTION = "After-treatment period"
|
|
55
|
+
FOLLOW_UP_DESCRIPTION = "Follow-up"
|
|
55
56
|
|
|
56
57
|
UNITS_DESCRIPTION = "Units"
|
|
57
58
|
|
|
@@ -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-07-
|
|
7
|
+
# Version: 2.4.2
|
|
8
|
+
# Last update: 2026-07-10 11:30
|
|
9
9
|
# Copyright (c) 2024-2026 Thomas Wieland
|
|
10
10
|
#-----------------------------------------------------------------------
|
|
11
11
|
|
|
@@ -898,6 +898,49 @@ class DiffModel:
|
|
|
898
898
|
|
|
899
899
|
treatment_effects = self.treatment_effects()
|
|
900
900
|
|
|
901
|
+
try:
|
|
902
|
+
if model_config.get("pre_post", False) and len(model_config.get("after_treatment_col", [])) > 0:
|
|
903
|
+
|
|
904
|
+
labels = treatment_effects.iloc[:, 0].astype(str).values
|
|
905
|
+
has_after = any((config.AFTER_TREATMENT_PERIOD_DESCRIPTION in lab) or (config.ATT_COL in lab) for lab in labels)
|
|
906
|
+
if not has_after:
|
|
907
|
+
model_results = self.data[0]
|
|
908
|
+
|
|
909
|
+
aate_key = config.EFFECTS_TYPES["AATE"]["model_results_key"] if "AATE" in config.EFFECTS_TYPES else "average_after_treatment_effects"
|
|
910
|
+
att_key = config.EFFECTS_TYPES["ATT"]["model_results_key"] if "ATT" in config.EFFECTS_TYPES else "after_treatment_time_effects"
|
|
911
|
+
appended = False
|
|
912
|
+
|
|
913
|
+
for key_try in [aate_key, att_key]:
|
|
914
|
+
if key_try in model_results and isinstance(model_results[key_try], dict):
|
|
915
|
+
for subkey, val in model_results[key_try].items():
|
|
916
|
+
|
|
917
|
+
if isinstance(val, dict):
|
|
918
|
+
|
|
919
|
+
coef_k = config.OLS_MODEL_RESULTS["coef"]["model_results_key"]
|
|
920
|
+
se_k = config.OLS_MODEL_RESULTS["coef_standard_errors"]["model_results_key"]
|
|
921
|
+
t_k = config.OLS_MODEL_RESULTS["coef_teststatistic"]["model_results_key"]
|
|
922
|
+
p_k = config.OLS_MODEL_RESULTS["coef_p"]["model_results_key"]
|
|
923
|
+
cil_k = config.OLS_MODEL_RESULTS["coef_confint_lower"]["model_results_key"]
|
|
924
|
+
ciu_k = config.OLS_MODEL_RESULTS["coef_confint_upper"]["model_results_key"]
|
|
925
|
+
|
|
926
|
+
new_row = {
|
|
927
|
+
"": f"{config.EFFECTS_TYPES.get('AATE', {'description': 'After-treatment'})['description']} {subkey}",
|
|
928
|
+
"Estimate": val.get(coef_k, np.nan),
|
|
929
|
+
"SE": val.get(se_k, np.nan),
|
|
930
|
+
"t": val.get(t_k, np.nan),
|
|
931
|
+
"p": val.get(p_k, np.nan),
|
|
932
|
+
"CI lower": val.get(cil_k, np.nan),
|
|
933
|
+
"CI upper": val.get(ciu_k, np.nan),
|
|
934
|
+
}
|
|
935
|
+
treatment_effects = pd.concat([treatment_effects, pd.DataFrame([new_row])], ignore_index=True)
|
|
936
|
+
appended = True
|
|
937
|
+
break
|
|
938
|
+
if appended:
|
|
939
|
+
break
|
|
940
|
+
except Exception:
|
|
941
|
+
|
|
942
|
+
pass
|
|
943
|
+
|
|
901
944
|
if sort_by_coef:
|
|
902
945
|
treatment_effects = treatment_effects.sort_values(
|
|
903
946
|
by = treatment_effects.columns[1],
|
|
@@ -1807,14 +1850,14 @@ class DiffModel:
|
|
|
1807
1850
|
model_data_TG_CG["t"] = pd.to_datetime(model_data_TG_CG["t"])
|
|
1808
1851
|
|
|
1809
1852
|
if not model_config["pre_post"]:
|
|
1810
|
-
pre_post_barplot = False
|
|
1853
|
+
pre_post_barplot = False
|
|
1811
1854
|
|
|
1812
1855
|
if pre_post_barplot:
|
|
1813
1856
|
|
|
1814
1857
|
x_pos_t1_TG = 0
|
|
1815
1858
|
x_pos_t1_CG = x_pos_t1_TG + pre_post_bar_width
|
|
1816
1859
|
x_pos_t2_TG = 1.5
|
|
1817
|
-
x_pos_t2_CG = x_pos_t2_TG + pre_post_bar_width
|
|
1860
|
+
x_pos_t2_CG = x_pos_t2_TG + pre_post_bar_width
|
|
1818
1861
|
|
|
1819
1862
|
plt.bar(
|
|
1820
1863
|
x = x_pos_t1_TG,
|
|
@@ -1842,6 +1885,24 @@ class DiffModel:
|
|
|
1842
1885
|
color=lines_col[3],
|
|
1843
1886
|
width = pre_post_bar_width
|
|
1844
1887
|
)
|
|
1888
|
+
|
|
1889
|
+
if len(model_data_TG_CG) == 3:
|
|
1890
|
+
|
|
1891
|
+
x_pos_t3_TG = 3
|
|
1892
|
+
x_pos_t3_CG = x_pos_t3_TG + pre_post_bar_width
|
|
1893
|
+
|
|
1894
|
+
plt.bar(
|
|
1895
|
+
x = x_pos_t3_TG,
|
|
1896
|
+
height = model_data_TG_CG[outcome_col_expected_TG][2],
|
|
1897
|
+
color = lines_col[2],
|
|
1898
|
+
width = pre_post_bar_width
|
|
1899
|
+
)
|
|
1900
|
+
plt.bar(
|
|
1901
|
+
x = x_pos_t3_CG,
|
|
1902
|
+
height = model_data_TG_CG[outcome_col_expected_CG][2],
|
|
1903
|
+
color=lines_col[3],
|
|
1904
|
+
width = pre_post_bar_width
|
|
1905
|
+
)
|
|
1845
1906
|
|
|
1846
1907
|
plt.xlabel(x_label)
|
|
1847
1908
|
plt.ylabel(y_label)
|
|
@@ -1935,16 +1996,29 @@ class DiffModel:
|
|
|
1935
1996
|
ax.xaxis.set_major_formatter(DateFormatter(model_config["date_format"]))
|
|
1936
1997
|
|
|
1937
1998
|
if model_config["pre_post"]:
|
|
1999
|
+
|
|
2000
|
+
ticks_line = model_data_TG_CG["t"].unique()
|
|
2001
|
+
ticks_bars = [0.25, 1.75]
|
|
2002
|
+
|
|
2003
|
+
if len(model_data_TG_CG) == 3:
|
|
2004
|
+
|
|
2005
|
+
if len(pre_post_ticks) < 3:
|
|
2006
|
+
pre_post_ticks.append("Follow-up")
|
|
2007
|
+
|
|
2008
|
+
ticks_bars.append(3.25)
|
|
2009
|
+
|
|
1938
2010
|
if not pre_post_barplot:
|
|
2011
|
+
|
|
1939
2012
|
plt.xticks(
|
|
1940
|
-
|
|
1941
|
-
labels =
|
|
2013
|
+
ticks_line,
|
|
2014
|
+
labels = pre_post_ticks
|
|
1942
2015
|
)
|
|
1943
2016
|
else:
|
|
1944
2017
|
plt.xticks(
|
|
1945
|
-
|
|
1946
|
-
labels =
|
|
1947
|
-
)
|
|
2018
|
+
ticks_bars,
|
|
2019
|
+
labels = pre_post_ticks
|
|
2020
|
+
)
|
|
2021
|
+
|
|
1948
2022
|
else:
|
|
1949
2023
|
plt.xticks(rotation=90)
|
|
1950
2024
|
|
|
@@ -2644,6 +2718,8 @@ def did_analysis(
|
|
|
2644
2718
|
FE_time_vars = []
|
|
2645
2719
|
dummy_time_original = []
|
|
2646
2720
|
|
|
2721
|
+
drop_first = intercept
|
|
2722
|
+
|
|
2647
2723
|
if not demean:
|
|
2648
2724
|
|
|
2649
2725
|
if FE_unit:
|
|
@@ -2652,7 +2728,7 @@ def did_analysis(
|
|
|
2652
2728
|
data = data,
|
|
2653
2729
|
col = unit_col,
|
|
2654
2730
|
type = "unit",
|
|
2655
|
-
drop_first =
|
|
2731
|
+
drop_first = drop_first,
|
|
2656
2732
|
verbose = verbose
|
|
2657
2733
|
)
|
|
2658
2734
|
|
|
@@ -2667,7 +2743,7 @@ def did_analysis(
|
|
|
2667
2743
|
data = data,
|
|
2668
2744
|
col = time_col,
|
|
2669
2745
|
type = "time",
|
|
2670
|
-
drop_first =
|
|
2746
|
+
drop_first = drop_first,
|
|
2671
2747
|
verbose = verbose
|
|
2672
2748
|
)
|
|
2673
2749
|
|
|
@@ -2691,7 +2767,7 @@ def did_analysis(
|
|
|
2691
2767
|
data = data,
|
|
2692
2768
|
col = group_by,
|
|
2693
2769
|
type = "group",
|
|
2694
|
-
drop_first =
|
|
2770
|
+
drop_first = drop_first,
|
|
2695
2771
|
verbose = verbose
|
|
2696
2772
|
)
|
|
2697
2773
|
|
|
@@ -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-07-
|
|
7
|
+
# Version: 2.3.1
|
|
8
|
+
# Last update: 2026-07-10 11:37
|
|
9
9
|
# Copyright (c) 2024-2026 Thomas Wieland
|
|
10
10
|
#-----------------------------------------------------------------------
|
|
11
11
|
|
|
@@ -622,15 +622,36 @@ def create_treatment(
|
|
|
622
622
|
|
|
623
623
|
if pre_post:
|
|
624
624
|
|
|
625
|
-
after_treatment_period
|
|
625
|
+
if after_treatment_period:
|
|
626
|
+
|
|
627
|
+
if study_period[1] <= treatment_period[1]:
|
|
628
|
+
|
|
629
|
+
print(f"WARNING: after_treatment_period is set True, but end of study period ({study_period[1]}) is <= end of treatment period ({treatment_period[1]}). After treatment period is ignored.")
|
|
630
|
+
|
|
631
|
+
after_treatment_period = False
|
|
632
|
+
|
|
633
|
+
else:
|
|
634
|
+
|
|
635
|
+
study_period_range = [study_period[0], study_period[1]]
|
|
636
|
+
study_period_N = 3
|
|
637
|
+
study_period_counter = [1,2,3]
|
|
626
638
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
639
|
+
treatment_period_N = 1
|
|
640
|
+
TT_dummies = [0,1,0]
|
|
641
|
+
ATT_dummies = [0,0,1]
|
|
630
642
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
643
|
+
treatment_period_range = [study_period[0], treatment_period[1], study_period[1]]
|
|
644
|
+
after_treatment_period_range = treatment_period_range
|
|
645
|
+
|
|
646
|
+
else:
|
|
647
|
+
|
|
648
|
+
study_period_range = [study_period[0], study_period[1]]
|
|
649
|
+
study_period_N = 2
|
|
650
|
+
study_period_counter = [1,2]
|
|
651
|
+
|
|
652
|
+
treatment_period_range = [treatment_period[0], treatment_period[1]]
|
|
653
|
+
treatment_period_N = 1
|
|
654
|
+
TT_dummies = [0,1]
|
|
634
655
|
|
|
635
656
|
study_period_range = pd.DataFrame (treatment_period_range, columns=[config.TIME_COL])
|
|
636
657
|
study_period_range[config.TIME_COUNTER_COL] = pd.DataFrame(study_period_counter)
|
|
@@ -640,7 +661,7 @@ def create_treatment(
|
|
|
640
661
|
TT_col: TT_dummies
|
|
641
662
|
}
|
|
642
663
|
|
|
643
|
-
TT_data = pd.DataFrame(TT_data)
|
|
664
|
+
TT_data = pd.DataFrame(TT_data)
|
|
644
665
|
|
|
645
666
|
treatment_period_range = pd.DataFrame(
|
|
646
667
|
study_period_range
|
|
@@ -650,6 +671,24 @@ def create_treatment(
|
|
|
650
671
|
TT_data,
|
|
651
672
|
how = "left"
|
|
652
673
|
)
|
|
674
|
+
|
|
675
|
+
if after_treatment_period:
|
|
676
|
+
|
|
677
|
+
ATT_data = {
|
|
678
|
+
config.TIME_COL: after_treatment_period_range,
|
|
679
|
+
ATT_col: ATT_dummies
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
ATT_data = pd.DataFrame(ATT_data)
|
|
683
|
+
|
|
684
|
+
after_treatment_period_range = pd.DataFrame (after_treatment_period_range, columns=[config.TIME_COL])
|
|
685
|
+
after_treatment_period_range[config.TIME_COUNTER_COL] = pd.DataFrame(study_period_counter)
|
|
686
|
+
|
|
687
|
+
after_treatment_data_df = after_treatment_period_range.merge(ATT_data, how = "left")
|
|
688
|
+
after_treatment_data_df[ATT_col] = after_treatment_data_df[ATT_col].fillna(0)
|
|
689
|
+
after_treatment_data_df = after_treatment_data_df.drop(columns=[config.TIME_COL, config.TIME_COUNTER_COL])
|
|
690
|
+
|
|
691
|
+
treatment_data_df = pd.concat([treatment_data_df, after_treatment_data_df], axis=1)
|
|
653
692
|
|
|
654
693
|
else:
|
|
655
694
|
|
|
@@ -689,7 +728,7 @@ def create_treatment(
|
|
|
689
728
|
|
|
690
729
|
treatment_data_df[TT_col] = treatment_data_df[TT_col].fillna(0)
|
|
691
730
|
|
|
692
|
-
if after_treatment_period:
|
|
731
|
+
if after_treatment_period and not pre_post:
|
|
693
732
|
|
|
694
733
|
treatment_period_last = datetime.strptime(
|
|
695
734
|
treatment_period[1],
|
|
@@ -720,6 +759,9 @@ def create_treatment(
|
|
|
720
759
|
|
|
721
760
|
else:
|
|
722
761
|
after_treatment_period_N = 0
|
|
762
|
+
|
|
763
|
+
if after_treatment_period and pre_post:
|
|
764
|
+
after_treatment_period_N = 1
|
|
723
765
|
|
|
724
766
|
no_treatments = 1
|
|
725
767
|
|
|
@@ -735,7 +777,7 @@ def create_treatment(
|
|
|
735
777
|
"date_format": date_format,
|
|
736
778
|
"pre_post": pre_post
|
|
737
779
|
}
|
|
738
|
-
|
|
780
|
+
|
|
739
781
|
treatment_config = {
|
|
740
782
|
0: {
|
|
741
783
|
"treatment_name": treatment_name,
|
|
@@ -2050,7 +2092,10 @@ class DiffData:
|
|
|
2050
2092
|
else:
|
|
2051
2093
|
print(f" {config.STUDY_PERIOD_DESCRIPTION:<{config.DIDDATA_SUMMARY_MAX_WIDTH}} {treatment_meta['study_period_start']} - {treatment_meta['study_period_end']} ({treatment_meta['study_period']} {treatment_meta['frequency']})")
|
|
2052
2094
|
print(f" {config.TREATMENT_PERIOD_DESCRIPTION:<{config.DIDDATA_SUMMARY_MAX_WIDTH}} {treatment_config[key]['treatment_period_start']} - {treatment_config[key]['treatment_period_end']} ({treatment_config[key]['treatment_period']} {treatment_meta['frequency']})")
|
|
2053
|
-
|
|
2095
|
+
|
|
2096
|
+
if value["after_treatment_period"]:
|
|
2097
|
+
print(f" {config.AFTER_TREATMENT_PERIOD_DESCRIPTION:<{config.DIDDATA_SUMMARY_MAX_WIDTH}} {treatment_config[key]['treatment_period_end']} vs. {treatment_meta['study_period_end']} ({config.FOLLOW_UP_DESCRIPTION})")
|
|
2098
|
+
|
|
2054
2099
|
print("-" * total_width)
|
|
2055
2100
|
|
|
2056
2101
|
print(f"Outcome '{outcome_col_original}'")
|
|
@@ -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.0
|
|
8
|
-
# Last update: 2026-
|
|
7
|
+
# Version: 2.1.0
|
|
8
|
+
# Last update: 2026-07-10 11:35
|
|
9
9
|
# Copyright (c) 2025-2026 Thomas Wieland
|
|
10
10
|
#-----------------------------------------------------------------------
|
|
11
11
|
|
|
@@ -75,7 +75,7 @@ curfew_data_prepost=create_data(
|
|
|
75
75
|
treatment_period=["2020-03-21", "2020-05-05"],
|
|
76
76
|
freq="D",
|
|
77
77
|
pre_post=True,
|
|
78
|
-
after_treatment_period =
|
|
78
|
+
after_treatment_period = False
|
|
79
79
|
)
|
|
80
80
|
# Creating DiD treatement dataset by defining groups and
|
|
81
81
|
# treatment time at once
|
|
@@ -98,6 +98,9 @@ print(curfew_model_prepost.covariates())
|
|
|
98
98
|
curfew_model_prepost.summary()
|
|
99
99
|
# Model summary
|
|
100
100
|
|
|
101
|
+
print(curfew_model_prepost.didmodel().summary())
|
|
102
|
+
# Access statsmodels OLS object
|
|
103
|
+
|
|
101
104
|
print(curfew_model_prepost.fit_metrics())
|
|
102
105
|
# Show model fit metrics
|
|
103
106
|
|
|
@@ -137,6 +140,59 @@ curfew_model_prepost.plot_treatment_effects(
|
|
|
137
140
|
)
|
|
138
141
|
# plot effects
|
|
139
142
|
|
|
143
|
+
|
|
144
|
+
# Same analysis with after-treatment period (follow-up):
|
|
145
|
+
|
|
146
|
+
curfew_data_prepost_AT=create_data(
|
|
147
|
+
outcome_data=curfew_DE,
|
|
148
|
+
unit_id_col="county",
|
|
149
|
+
time_col="infection_date",
|
|
150
|
+
outcome_col="infections_cum_per100000",
|
|
151
|
+
treatment_group=
|
|
152
|
+
curfew_DE.loc[curfew_DE["Bundesland"].isin([9,10,14])]["county"],
|
|
153
|
+
control_group=
|
|
154
|
+
curfew_DE.loc[~curfew_DE["Bundesland"].isin([9,10,14])]["county"],
|
|
155
|
+
study_period=["2020-03-01", "2020-05-15"],
|
|
156
|
+
treatment_period=["2020-03-21", "2020-05-05"],
|
|
157
|
+
freq="D",
|
|
158
|
+
pre_post=True,
|
|
159
|
+
after_treatment_period = True
|
|
160
|
+
)
|
|
161
|
+
# Creating DiD treatement dataset by defining groups and
|
|
162
|
+
# treatment time at once
|
|
163
|
+
|
|
164
|
+
curfew_data_prepost_AT.summary()
|
|
165
|
+
# Summary of created data
|
|
166
|
+
|
|
167
|
+
curfew_model_prepost_AT=curfew_data_prepost_AT.analysis(verbose=True)
|
|
168
|
+
# Model analysis of created data
|
|
169
|
+
|
|
170
|
+
curfew_model_prepost_AT.summary()
|
|
171
|
+
# Model summary
|
|
172
|
+
|
|
173
|
+
curfew_model_prepost_AT.plot(
|
|
174
|
+
x_label="Timepoint",
|
|
175
|
+
y_label="Cumulative infections per 100,000",
|
|
176
|
+
plot_title="Curfew effectiveness pre-post - Groups over time",
|
|
177
|
+
plot_observed=False,
|
|
178
|
+
lines_col=[None,None,"blue","orange"],
|
|
179
|
+
lines_labels=[None,None,"Treatment group","Control group","Treatment group CI","Control group CI"],
|
|
180
|
+
lines_style=[None,None,"solid","solid"]
|
|
181
|
+
)
|
|
182
|
+
# Plot DiD pre vs. post results
|
|
183
|
+
# with user-determined style
|
|
184
|
+
|
|
185
|
+
curfew_model_prepost_AT.plot(
|
|
186
|
+
x_label="Timepoint",
|
|
187
|
+
y_label="Cumulative infections per 100,000",
|
|
188
|
+
plot_title="Curfew effectiveness pre-post - Groups over time",
|
|
189
|
+
lines_col=[None,None,"blue","orange"],
|
|
190
|
+
lines_labels=[None,None,"Treatment group","Control group","Treatment group CI", "Control group CI"],
|
|
191
|
+
pre_post_barplot=True
|
|
192
|
+
)
|
|
193
|
+
# Plot DiD pre vs. post results
|
|
194
|
+
# with user-determined style
|
|
195
|
+
|
|
140
196
|
counties_DE=pd.read_csv("data/counties_DE.csv", sep=";", decimal=",", encoding='latin1')
|
|
141
197
|
# Dataset with German county data
|
|
142
198
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: diffindiff
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.1
|
|
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 research note featuring a case study that utilizes the diffindiff library is a
|
|
|
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.1) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.18656820
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
## Installation
|
|
@@ -177,16 +177,9 @@ See the /tests directory for usage examples of most of the included functions.
|
|
|
177
177
|
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).
|
|
178
178
|
|
|
179
179
|
|
|
180
|
-
## What's new (v2.5.
|
|
180
|
+
## What's new (v2.5.1)
|
|
181
181
|
|
|
182
|
-
- General
|
|
183
|
-
- Update of dependencies to be compatible with Python 3.13 (still works with Python 3.11) while avoiding incompatibility conflict of statsmodels and scipy
|
|
184
|
-
- Internal changes in some functions to be compatible with Python 3.13 (still works with Python 3.11)
|
|
185
|
-
- Extensions
|
|
186
|
-
- Creating synthetic control units with DiffData.add_synthetic() and conducting a Synthetic DiD analysis
|
|
187
|
-
- didanalysis_helper.treatment_diagnostics() now additionally checks unique number of analysis units and time points
|
|
188
|
-
- didanalysis_helper.data_diagnostics() now additionally checks whether covariates are constant; didanalysis.did_analysis() and .ddd_analysis() automatically skip such variables from the model analysis
|
|
189
182
|
- Bugfixes
|
|
190
|
-
- DiffData.
|
|
191
|
-
-
|
|
192
|
-
-
|
|
183
|
+
- In DiffData.create_treatment() and DiffData.create_data(), pre-post designs with after-treatment period (follow-up) now work correctly
|
|
184
|
+
- DiffModel.plot() nows supports pre-post designs with after-treatment period
|
|
185
|
+
- Some small stabilizations
|
|
@@ -7,7 +7,7 @@ def read_README():
|
|
|
7
7
|
|
|
8
8
|
setup(
|
|
9
9
|
name='diffindiff',
|
|
10
|
-
version='2.5.
|
|
10
|
+
version='2.5.1',
|
|
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,
|
|
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
|
|
File without changes
|
|
File without changes
|