diffindiff 2.3.0__tar.gz → 2.3.2__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.3.0 → diffindiff-2.3.2}/PKG-INFO +13 -21
- {diffindiff-2.3.0 → diffindiff-2.3.2}/README.md +12 -20
- {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/config.py +3 -3
- {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/didanalysis.py +80 -32
- {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/diddata.py +36 -32
- {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/didtools.py +67 -27
- {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff.egg-info/PKG-INFO +13 -21
- {diffindiff-2.3.0 → diffindiff-2.3.2}/setup.py +1 -1
- {diffindiff-2.3.0 → diffindiff-2.3.2}/MANIFEST.in +0 -0
- {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/__init__.py +0 -0
- {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/didanalysis_helper.py +0 -0
- {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/tests/__init__.py +0 -0
- {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/tests/data/Corona_Hesse.xlsx +0 -0
- {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/tests/data/counties_DE.csv +0 -0
- {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/tests/data/curfew_DE.csv +0 -0
- {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/tests/tests_diffindiff.py +0 -0
- {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff.egg-info/SOURCES.txt +0 -0
- {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff.egg-info/dependency_links.txt +0 -0
- {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff.egg-info/requires.txt +0 -0
- {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff.egg-info/top_level.txt +0 -0
- {diffindiff-2.3.0 → diffindiff-2.3.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: diffindiff
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.2
|
|
4
4
|
Summary: diffindiff: Python library for convenient Difference-in-Differences analyses
|
|
5
5
|
Author: Thomas Wieland
|
|
6
6
|
Author-email: geowieland@googlemail.com
|
|
@@ -27,7 +27,7 @@ Thomas Wieland [ORCID](https://orcid.org/0000-0001-5168-9846) [EMail](mailto:geo
|
|
|
27
27
|
|
|
28
28
|
If you use this software, please cite:
|
|
29
29
|
|
|
30
|
-
Wieland, T. (2026). diffindiff: A Python library for convenient difference-in-differences analyses (Version 2.3.
|
|
30
|
+
Wieland, T. (2026). diffindiff: A Python library for convenient difference-in-differences analyses (Version 2.3.2) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.18656820
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
## Installation
|
|
@@ -52,10 +52,11 @@ pip install git+https://github.com/geowieland/diffindiff_official.git
|
|
|
52
52
|
- Create ready-to-fit DiD data objects
|
|
53
53
|
- Create predictive counterfactuals
|
|
54
54
|
- **DiD analysis**:
|
|
55
|
-
-
|
|
56
|
-
-
|
|
55
|
+
- Perform standard DiD analysis with pre-post data
|
|
56
|
+
- Perform DiD analysis with two-way fixed effects models
|
|
57
57
|
- Simultaneous and/or staggered adoption are supported
|
|
58
58
|
- Single or multiple treatments are supported
|
|
59
|
+
- Binary or continuous treatments are supported
|
|
59
60
|
- Model extensions for DiD analysis:
|
|
60
61
|
- Group- or individual-specific treatment effects
|
|
61
62
|
- Group- or individual-specific time trends
|
|
@@ -66,10 +67,10 @@ pip install git+https://github.com/geowieland/diffindiff_official.git
|
|
|
66
67
|
- Add own counterfactuals or create counterfactuals based on machine learning or OLS regression models
|
|
67
68
|
- Bonferroni correction for treatment effects
|
|
68
69
|
- Placebo test
|
|
69
|
-
- Test for control conditions
|
|
70
|
-
- Test for type of adoption
|
|
71
|
-
- Test whether the panel dataset is balanced
|
|
72
|
-
- Test for parallel trend assumption
|
|
70
|
+
- Test for control conditions (automatically within analysis or stand-alone)
|
|
71
|
+
- Test for type of adoption (automatically within analysis or stand-alone)
|
|
72
|
+
- Test whether the panel dataset is balanced (automatically within analysis or stand-alone)
|
|
73
|
+
- Test for parallel trend assumption (automatically within analysis or stand-alone)
|
|
73
74
|
- **Visualization**:
|
|
74
75
|
- Plot observed and expected time course of treatment and control group
|
|
75
76
|
- Plot expected time course of treatment group and counterfactual
|
|
@@ -169,19 +170,10 @@ See the /tests directory for usage examples of most of the included functions.
|
|
|
169
170
|
|
|
170
171
|
## AI Usage Statement
|
|
171
172
|
|
|
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 `
|
|
173
|
+
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).
|
|
173
174
|
|
|
174
175
|
|
|
175
|
-
## What's new (v2.3.
|
|
176
|
+
## What's new (v2.3.2)
|
|
176
177
|
|
|
177
|
-
-
|
|
178
|
-
-
|
|
179
|
-
- Extended information in README
|
|
180
|
-
- Extensions
|
|
181
|
-
- Timestamps for all methods creating or changing DiffGroups, DiffTreatment, DiffData and DiffModel objects
|
|
182
|
-
- Hyperparameters and other config information in didtools.model_wrapper() are now saved
|
|
183
|
-
- All summary methods return self
|
|
184
|
-
- Bugfixes:
|
|
185
|
-
- Correct check of input lists in didanalysis_helper functions create_spillover() and extract_model_results()
|
|
186
|
-
- Correct check of input lists in DiffData methods add_covariates() and analysis()
|
|
187
|
-
- Correct check of input lists in didanalysis functions did_analysis() and ddd_analysis()
|
|
178
|
+
- Extensions:
|
|
179
|
+
- Re-transform log-transformed outcomes in DiffModel.plot() via parameter 'retransform_log_outcome'
|
|
@@ -19,7 +19,7 @@ Thomas Wieland [ORCID](https://orcid.org/0000-0001-5168-9846) [EMail](mailto:geo
|
|
|
19
19
|
|
|
20
20
|
If you use this software, please cite:
|
|
21
21
|
|
|
22
|
-
Wieland, T. (2026). diffindiff: A Python library for convenient difference-in-differences analyses (Version 2.3.
|
|
22
|
+
Wieland, T. (2026). diffindiff: A Python library for convenient difference-in-differences analyses (Version 2.3.2) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.18656820
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
## Installation
|
|
@@ -44,10 +44,11 @@ pip install git+https://github.com/geowieland/diffindiff_official.git
|
|
|
44
44
|
- Create ready-to-fit DiD data objects
|
|
45
45
|
- Create predictive counterfactuals
|
|
46
46
|
- **DiD analysis**:
|
|
47
|
-
-
|
|
48
|
-
-
|
|
47
|
+
- Perform standard DiD analysis with pre-post data
|
|
48
|
+
- Perform DiD analysis with two-way fixed effects models
|
|
49
49
|
- Simultaneous and/or staggered adoption are supported
|
|
50
50
|
- Single or multiple treatments are supported
|
|
51
|
+
- Binary or continuous treatments are supported
|
|
51
52
|
- Model extensions for DiD analysis:
|
|
52
53
|
- Group- or individual-specific treatment effects
|
|
53
54
|
- Group- or individual-specific time trends
|
|
@@ -58,10 +59,10 @@ pip install git+https://github.com/geowieland/diffindiff_official.git
|
|
|
58
59
|
- Add own counterfactuals or create counterfactuals based on machine learning or OLS regression models
|
|
59
60
|
- Bonferroni correction for treatment effects
|
|
60
61
|
- Placebo test
|
|
61
|
-
- Test for control conditions
|
|
62
|
-
- Test for type of adoption
|
|
63
|
-
- Test whether the panel dataset is balanced
|
|
64
|
-
- Test for parallel trend assumption
|
|
62
|
+
- Test for control conditions (automatically within analysis or stand-alone)
|
|
63
|
+
- Test for type of adoption (automatically within analysis or stand-alone)
|
|
64
|
+
- Test whether the panel dataset is balanced (automatically within analysis or stand-alone)
|
|
65
|
+
- Test for parallel trend assumption (automatically within analysis or stand-alone)
|
|
65
66
|
- **Visualization**:
|
|
66
67
|
- Plot observed and expected time course of treatment and control group
|
|
67
68
|
- Plot expected time course of treatment group and counterfactual
|
|
@@ -161,19 +162,10 @@ See the /tests directory for usage examples of most of the included functions.
|
|
|
161
162
|
|
|
162
163
|
## AI Usage Statement
|
|
163
164
|
|
|
164
|
-
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 `
|
|
165
|
+
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).
|
|
165
166
|
|
|
166
167
|
|
|
167
|
-
## What's new (v2.3.
|
|
168
|
+
## What's new (v2.3.2)
|
|
168
169
|
|
|
169
|
-
-
|
|
170
|
-
-
|
|
171
|
-
- Extended information in README
|
|
172
|
-
- Extensions
|
|
173
|
-
- Timestamps for all methods creating or changing DiffGroups, DiffTreatment, DiffData and DiffModel objects
|
|
174
|
-
- Hyperparameters and other config information in didtools.model_wrapper() are now saved
|
|
175
|
-
- All summary methods return self
|
|
176
|
-
- Bugfixes:
|
|
177
|
-
- Correct check of input lists in didanalysis_helper functions create_spillover() and extract_model_results()
|
|
178
|
-
- Correct check of input lists in DiffData methods add_covariates() and analysis()
|
|
179
|
-
- Correct check of input lists in didanalysis functions did_analysis() and ddd_analysis()
|
|
170
|
+
- Extensions:
|
|
171
|
+
- Re-transform log-transformed outcomes in DiffModel.plot() via parameter 'retransform_log_outcome'
|
|
@@ -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.10
|
|
8
|
+
# Last update: 2026-03-05 21:27
|
|
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.3.
|
|
15
|
+
PACKAGE_VERSION = "2.3.2"
|
|
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.3.
|
|
8
|
-
# Last update: 2026-03-
|
|
7
|
+
# Version: 2.3.2
|
|
8
|
+
# Last update: 2026-03-06 21:26
|
|
9
9
|
# Copyright (c) 2024-2026 Thomas Wieland
|
|
10
10
|
#-----------------------------------------------------------------------
|
|
11
11
|
|
|
@@ -186,12 +186,23 @@ class DiffModel:
|
|
|
186
186
|
study_period_end = study_period_end.date()
|
|
187
187
|
study_period_N = model_data[time_col].nunique()
|
|
188
188
|
|
|
189
|
-
if
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
189
|
+
if config.ACCEPT_CONTINUOUS_TREATMENTS:
|
|
190
|
+
|
|
191
|
+
if len(model_data[model_data[treatment] > 0]) > 0:
|
|
192
|
+
treatment_period_start = pd.to_datetime(min(model_data[model_data[treatment] > 0][time_col]))
|
|
193
|
+
treatment_period_end = pd.to_datetime(max(model_data[model_data[treatment] > 0][time_col]))
|
|
194
|
+
treatment_period_N = model_data.loc[model_data[treatment] > 0, time_col].nunique()
|
|
195
|
+
else:
|
|
196
|
+
treatment_period_N = 0
|
|
197
|
+
|
|
193
198
|
else:
|
|
194
|
-
|
|
199
|
+
|
|
200
|
+
if len(model_data[model_data[treatment] == 1]) > 0:
|
|
201
|
+
treatment_period_start = pd.to_datetime(min(model_data[model_data[treatment] == 1][time_col]))
|
|
202
|
+
treatment_period_end = pd.to_datetime(max(model_data[model_data[treatment] == 1][time_col]))
|
|
203
|
+
treatment_period_N = model_data.loc[model_data[treatment] == 1, time_col].nunique()
|
|
204
|
+
else:
|
|
205
|
+
treatment_period_N = 0
|
|
195
206
|
|
|
196
207
|
after_treatment_period_start = None
|
|
197
208
|
after_treatment_period_end = None
|
|
@@ -200,7 +211,10 @@ class DiffModel:
|
|
|
200
211
|
after_treatment_period_start = treatment_period_end+pd.Timedelta(days=1)
|
|
201
212
|
after_treatment_period_start = pd.to_datetime(after_treatment_period_start)
|
|
202
213
|
after_treatment_period_end = pd.to_datetime(study_period_end)
|
|
203
|
-
|
|
214
|
+
if config.ACCEPT_CONTINUOUS_TREATMENTS:
|
|
215
|
+
after_treatment_period_N = model_data.loc[model_data[after_treatment_col] > 0, time_col].nunique()
|
|
216
|
+
else:
|
|
217
|
+
after_treatment_period_N = model_data.loc[model_data[after_treatment_col] == 1, time_col].nunique()
|
|
204
218
|
after_treatment_period_start = after_treatment_period_start.strftime(model_config["date_format"])
|
|
205
219
|
after_treatment_period_end = after_treatment_period_end.strftime(model_config["date_format"])
|
|
206
220
|
|
|
@@ -1151,7 +1165,7 @@ class DiffModel:
|
|
|
1151
1165
|
|
|
1152
1166
|
def prediction_intervals(
|
|
1153
1167
|
self,
|
|
1154
|
-
confint_alpha = 0.05
|
|
1168
|
+
confint_alpha: float = 0.05
|
|
1155
1169
|
):
|
|
1156
1170
|
|
|
1157
1171
|
"""
|
|
@@ -1345,11 +1359,11 @@ class DiffModel:
|
|
|
1345
1359
|
self,
|
|
1346
1360
|
treatment: str = None,
|
|
1347
1361
|
TG_col: str = None,
|
|
1348
|
-
x_label = "Time",
|
|
1349
|
-
y_label = "Analysis units",
|
|
1362
|
+
x_label: str = "Time",
|
|
1363
|
+
y_label: str = "Analysis units",
|
|
1350
1364
|
y_lim = None,
|
|
1351
|
-
plot_title = "Treatment time",
|
|
1352
|
-
plot_symbol = "o",
|
|
1365
|
+
plot_title: str = "Treatment time",
|
|
1366
|
+
plot_symbol: str = "o",
|
|
1353
1367
|
treatment_group_only = True
|
|
1354
1368
|
):
|
|
1355
1369
|
|
|
@@ -1422,7 +1436,12 @@ class DiffModel:
|
|
|
1422
1436
|
modeldata_pivot.index = pd.to_datetime(modeldata_pivot.index)
|
|
1423
1437
|
|
|
1424
1438
|
for i, col in enumerate(modeldata_pivot.columns):
|
|
1425
|
-
|
|
1439
|
+
|
|
1440
|
+
if config.ACCEPT_CONTINUOUS_TREATMENTS:
|
|
1441
|
+
time_points_treatment = modeldata_pivot.index[modeldata_pivot[col] > 0]
|
|
1442
|
+
else:
|
|
1443
|
+
time_points_treatment = modeldata_pivot.index[modeldata_pivot[col] == 1]
|
|
1444
|
+
|
|
1426
1445
|
values = [i] * len(time_points_treatment)
|
|
1427
1446
|
ax.plot(time_points_treatment, values, plot_symbol, label=col)
|
|
1428
1447
|
|
|
@@ -1466,7 +1485,8 @@ class DiffModel:
|
|
|
1466
1485
|
plot_size: list = [12, 6],
|
|
1467
1486
|
pre_post_ticks: list = ["Pre", "Post"],
|
|
1468
1487
|
pre_post_barplot = False,
|
|
1469
|
-
pre_post_bar_width = 0.5
|
|
1488
|
+
pre_post_bar_width = 0.5,
|
|
1489
|
+
retransform_log_outcome: bool = False
|
|
1470
1490
|
):
|
|
1471
1491
|
|
|
1472
1492
|
"""
|
|
@@ -1510,6 +1530,9 @@ class DiffModel:
|
|
|
1510
1530
|
Plot pre-post as barplot. Default is False.
|
|
1511
1531
|
pre_post_bar_width : float, optional
|
|
1512
1532
|
Bar width when pre_post_barplot is True. Default is 0.5.
|
|
1533
|
+
retransform_log_outcome : bool, optional
|
|
1534
|
+
If outcome was log-transformed, retransform to original scale for plotting.
|
|
1535
|
+
Default is False.
|
|
1513
1536
|
|
|
1514
1537
|
Returns
|
|
1515
1538
|
-------
|
|
@@ -1593,6 +1616,23 @@ class DiffModel:
|
|
|
1593
1616
|
|
|
1594
1617
|
model_data = pd.concat ([model_data, model_predictions], axis = 1)
|
|
1595
1618
|
|
|
1619
|
+
if retransform_log_outcome:
|
|
1620
|
+
|
|
1621
|
+
if model_config["log_outcome"]:
|
|
1622
|
+
|
|
1623
|
+
model_data[outcome_col] = np.exp(model_data[outcome_col])
|
|
1624
|
+
model_data[outcome_col_predicted] = np.exp(model_data[outcome_col_predicted])
|
|
1625
|
+
|
|
1626
|
+
model_data[config.PREDICTIONS_SUMMARY_FRAME_COLS_LIST[2]] = np.exp(model_data[config.PREDICTIONS_SUMMARY_FRAME_COLS_LIST[2]])
|
|
1627
|
+
model_data[config.PREDICTIONS_SUMMARY_FRAME_COLS_LIST[3]] = np.exp(model_data[config.PREDICTIONS_SUMMARY_FRAME_COLS_LIST[3]])
|
|
1628
|
+
model_data[config.PREDICTIONS_SUMMARY_FRAME_COLS_LIST[4]] = np.exp(model_data[config.PREDICTIONS_SUMMARY_FRAME_COLS_LIST[4]])
|
|
1629
|
+
model_data[config.PREDICTIONS_SUMMARY_FRAME_COLS_LIST[5]] = np.exp(model_data[config.PREDICTIONS_SUMMARY_FRAME_COLS_LIST[5]])
|
|
1630
|
+
|
|
1631
|
+
print("NOTE: Outcome variable was log-transformed, Re-transformation was applied.")
|
|
1632
|
+
|
|
1633
|
+
else:
|
|
1634
|
+
print("NOTE: Parameter 'retransform_log_outcome' was set to True, but outcome variable was not log-transformed. No re-transformation applied.")
|
|
1635
|
+
|
|
1596
1636
|
model_data_TG = model_data[model_data[TG_col] == 1]
|
|
1597
1637
|
model_data_CG = model_data[model_data[TG_col] == 0]
|
|
1598
1638
|
|
|
@@ -2230,16 +2270,20 @@ def did_analysis(
|
|
|
2230
2270
|
treatment_diagnostics = treatment_diagnostics_results[0]
|
|
2231
2271
|
staggered_adoption = treatment_diagnostics_results[1]
|
|
2232
2272
|
|
|
2233
|
-
if no_treatments > 1:
|
|
2234
|
-
|
|
2273
|
+
if no_treatments > 1:
|
|
2274
|
+
|
|
2235
2275
|
intercept = False
|
|
2236
|
-
TG_col = []
|
|
2237
|
-
|
|
2238
|
-
|
|
2276
|
+
TG_col = []
|
|
2277
|
+
|
|
2278
|
+
if not FE_unit:
|
|
2279
|
+
FE_unit = True
|
|
2280
|
+
print("NOTE: Quasi-experiment includes more than one treatment. Unit fixed effects are used instead of control group baseline and treatment group deviation.")
|
|
2281
|
+
|
|
2239
2282
|
if ITE:
|
|
2240
2283
|
|
|
2241
|
-
|
|
2242
|
-
|
|
2284
|
+
if not FE_unit:
|
|
2285
|
+
FE_unit = True
|
|
2286
|
+
print("NOTE: Model includes individual treatment effects. Unit fixed effects are included.")
|
|
2243
2287
|
|
|
2244
2288
|
if GTE:
|
|
2245
2289
|
GTE = False
|
|
@@ -2247,15 +2291,15 @@ def did_analysis(
|
|
|
2247
2291
|
|
|
2248
2292
|
if ITT:
|
|
2249
2293
|
|
|
2250
|
-
FE_unit = True
|
|
2251
|
-
|
|
2252
2294
|
TT_col = []
|
|
2253
2295
|
|
|
2254
|
-
|
|
2296
|
+
if not FE_unit:
|
|
2297
|
+
FE_unit = True
|
|
2298
|
+
print("NOTE: Model includes individual time trends. Unit fixed effects are included. Treatment time variable is dropped.")
|
|
2255
2299
|
|
|
2256
2300
|
if FE_time:
|
|
2257
2301
|
FE_time = False
|
|
2258
|
-
print("NOTE: Time fixed effects are dropped.")
|
|
2302
|
+
print("NOTE: Model includes individual time trends. Time fixed effects are dropped.")
|
|
2259
2303
|
|
|
2260
2304
|
if GTT:
|
|
2261
2305
|
GTT = False
|
|
@@ -2263,10 +2307,11 @@ def did_analysis(
|
|
|
2263
2307
|
|
|
2264
2308
|
if staggered_adoption:
|
|
2265
2309
|
|
|
2310
|
+
if not FE_unit or not FE_time:
|
|
2311
|
+
print("NOTE: Quasi-experiment includes one or more staggered treatments. Two-way fixed effects model is used.")
|
|
2312
|
+
|
|
2266
2313
|
FE_unit = True
|
|
2267
|
-
FE_time = True
|
|
2268
|
-
|
|
2269
|
-
print("NOTE: Quasi-experiment includes one or more staggered treatments. Two-way fixed effects model is used.")
|
|
2314
|
+
FE_time = True
|
|
2270
2315
|
|
|
2271
2316
|
if GTT or GTE:
|
|
2272
2317
|
FE_group = True
|
|
@@ -2277,10 +2322,13 @@ def did_analysis(
|
|
|
2277
2322
|
if FE_time:
|
|
2278
2323
|
TT_col = []
|
|
2279
2324
|
|
|
2280
|
-
if FE_group:
|
|
2325
|
+
if FE_group:
|
|
2326
|
+
|
|
2327
|
+
if intercept or len(TG_col) > 0:
|
|
2328
|
+
print("NOTE: Quasi-experiment includes group fixed effects. Control group baseline and treatment group deviation are dropped.")
|
|
2329
|
+
|
|
2281
2330
|
TG_col = []
|
|
2282
|
-
intercept = False
|
|
2283
|
-
print("NOTE: Quasi-experiment includes group fixed effects. Control group baseline and treatment group deviation are dropped.")
|
|
2331
|
+
intercept = False
|
|
2284
2332
|
|
|
2285
2333
|
if after_treatment_col is not None or (isinstance (after_treatment_col, list) and len(after_treatment_col) > 0):
|
|
2286
2334
|
|
|
@@ -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.2.
|
|
8
|
-
# Last update: 2026-03-
|
|
7
|
+
# Version: 2.2.2
|
|
8
|
+
# Last update: 2026-03-06 21:27
|
|
9
9
|
# Copyright (c) 2024-2026 Thomas Wieland
|
|
10
10
|
#-----------------------------------------------------------------------
|
|
11
11
|
|
|
@@ -174,7 +174,7 @@ class DiffGroups:
|
|
|
174
174
|
def add_segmentation(
|
|
175
175
|
self,
|
|
176
176
|
group_benefit: list,
|
|
177
|
-
verbose: bool =
|
|
177
|
+
verbose: bool = False
|
|
178
178
|
):
|
|
179
179
|
|
|
180
180
|
"""
|
|
@@ -236,7 +236,7 @@ def create_groups(
|
|
|
236
236
|
treatment_group,
|
|
237
237
|
control_group,
|
|
238
238
|
treatment_name: str = None,
|
|
239
|
-
verbose: bool =
|
|
239
|
+
verbose: bool = False
|
|
240
240
|
):
|
|
241
241
|
|
|
242
242
|
"""
|
|
@@ -538,7 +538,7 @@ def create_treatment(
|
|
|
538
538
|
treatment_name: str = None,
|
|
539
539
|
pre_post: bool = False,
|
|
540
540
|
after_treatment_period: bool = False,
|
|
541
|
-
verbose =
|
|
541
|
+
verbose = False
|
|
542
542
|
):
|
|
543
543
|
|
|
544
544
|
"""
|
|
@@ -1181,7 +1181,7 @@ class DiffData:
|
|
|
1181
1181
|
print(f"WARNING: Additional data frame includes duplicate column names: {', '.join(existing_variables)}")
|
|
1182
1182
|
|
|
1183
1183
|
return self
|
|
1184
|
-
|
|
1184
|
+
|
|
1185
1185
|
def add_treatment(
|
|
1186
1186
|
self,
|
|
1187
1187
|
treatment_name: str,
|
|
@@ -1190,7 +1190,7 @@ class DiffData:
|
|
|
1190
1190
|
control_group,
|
|
1191
1191
|
after_treatment_period: bool = False,
|
|
1192
1192
|
after_treatment_name = None,
|
|
1193
|
-
verbose: bool =
|
|
1193
|
+
verbose: bool = False
|
|
1194
1194
|
):
|
|
1195
1195
|
|
|
1196
1196
|
"""
|
|
@@ -1389,7 +1389,7 @@ class DiffData:
|
|
|
1389
1389
|
treatment_name: str,
|
|
1390
1390
|
after_treatment_period: bool = False,
|
|
1391
1391
|
after_treatment_name: str = None,
|
|
1392
|
-
verbose: bool =
|
|
1392
|
+
verbose: bool = False
|
|
1393
1393
|
):
|
|
1394
1394
|
|
|
1395
1395
|
"""
|
|
@@ -1420,25 +1420,25 @@ class DiffData:
|
|
|
1420
1420
|
|
|
1421
1421
|
Examples
|
|
1422
1422
|
--------
|
|
1423
|
-
>>>
|
|
1424
|
-
... outcome_data=
|
|
1425
|
-
... unit_id_col="
|
|
1426
|
-
... time_col="
|
|
1427
|
-
... outcome_col="
|
|
1428
|
-
... treatment_group=
|
|
1429
|
-
... control_group=
|
|
1430
|
-
... study_period=["
|
|
1431
|
-
... treatment_period=["
|
|
1432
|
-
... freq="
|
|
1433
|
-
...
|
|
1423
|
+
>>> curfew_data_prepost=create_data(
|
|
1424
|
+
... outcome_data=curfew_DE,
|
|
1425
|
+
... unit_id_col="county",
|
|
1426
|
+
... time_col="infection_date",
|
|
1427
|
+
... outcome_col="infections_cum_per100000",
|
|
1428
|
+
... treatment_group=curfew_DE.loc[curfew_DE["Bundesland"].isin([9,10,14])]["county"],
|
|
1429
|
+
... control_group=curfew_DE.loc[~curfew_DE["Bundesland"].isin([9,10,14])]["county"],
|
|
1430
|
+
... study_period=["2020-03-01", "2020-05-15"],
|
|
1431
|
+
... treatment_period=["2020-03-21", "2020-05-05"],
|
|
1432
|
+
... freq="D",
|
|
1433
|
+
... pre_post=True
|
|
1434
1434
|
... )
|
|
1435
|
-
>>>
|
|
1436
|
-
... additional_df=
|
|
1437
|
-
...
|
|
1438
|
-
...
|
|
1439
|
-
...
|
|
1435
|
+
>>> curfew_data_prepost_withcov = curfew_data_prepost.add_covariates(
|
|
1436
|
+
... additional_df=counties_DE,
|
|
1437
|
+
... unit_col="county",
|
|
1438
|
+
... time_col=None,
|
|
1439
|
+
... variables=["comm_index", "TourPer1000"]
|
|
1440
1440
|
... )
|
|
1441
|
-
>>>
|
|
1441
|
+
>>> curfew_data_prepost_withcov.define_treatment("TourPer1000")
|
|
1442
1442
|
"""
|
|
1443
1443
|
|
|
1444
1444
|
if not treatment_name:
|
|
@@ -1613,7 +1613,7 @@ class DiffData:
|
|
|
1613
1613
|
counterfactual_outcome_col: str,
|
|
1614
1614
|
time_col: str,
|
|
1615
1615
|
counterfactual_UID: str = "counterfac",
|
|
1616
|
-
verbose: bool =
|
|
1616
|
+
verbose: bool = False
|
|
1617
1617
|
):
|
|
1618
1618
|
|
|
1619
1619
|
"""
|
|
@@ -1798,7 +1798,8 @@ class DiffData:
|
|
|
1798
1798
|
|
|
1799
1799
|
def analysis(
|
|
1800
1800
|
self,
|
|
1801
|
-
log_outcome: bool = False,
|
|
1801
|
+
log_outcome: bool = False,
|
|
1802
|
+
log_outcome_add: float = 0.01,
|
|
1802
1803
|
FE_unit: bool = False,
|
|
1803
1804
|
FE_time: bool = False,
|
|
1804
1805
|
cluster_SE_by: str = None,
|
|
@@ -1814,7 +1815,7 @@ class DiffData:
|
|
|
1814
1815
|
bonferroni: bool = False,
|
|
1815
1816
|
drop_missing: bool = True,
|
|
1816
1817
|
missing_replace_by_zero: bool = False,
|
|
1817
|
-
verbose: bool =
|
|
1818
|
+
verbose: bool = False
|
|
1818
1819
|
):
|
|
1819
1820
|
|
|
1820
1821
|
"""
|
|
@@ -1825,6 +1826,8 @@ class DiffData:
|
|
|
1825
1826
|
----------
|
|
1826
1827
|
log_outcome : bool, optional
|
|
1827
1828
|
If True, log-transform the outcome variable before estimation.
|
|
1829
|
+
log_outcome_add : float, optional
|
|
1830
|
+
Add a constant to outcome variable before log-transform.
|
|
1828
1831
|
FE_unit : bool, optional
|
|
1829
1832
|
Include unit fixed effects.
|
|
1830
1833
|
FE_time : bool, optional
|
|
@@ -1914,7 +1917,7 @@ class DiffData:
|
|
|
1914
1917
|
BG_col = config.BG_COL,
|
|
1915
1918
|
outcome_col = outcome_col_original,
|
|
1916
1919
|
log_outcome = log_outcome,
|
|
1917
|
-
log_outcome_add =
|
|
1920
|
+
log_outcome_add = log_outcome_add,
|
|
1918
1921
|
FE_unit = FE_unit,
|
|
1919
1922
|
FE_time = FE_time,
|
|
1920
1923
|
covariates = covariates,
|
|
@@ -1961,6 +1964,7 @@ class DiffData:
|
|
|
1961
1964
|
ATT_col = ATT_col,
|
|
1962
1965
|
pre_post = treatment_meta["pre_post"],
|
|
1963
1966
|
log_outcome = log_outcome,
|
|
1967
|
+
log_outcome_add = log_outcome_add,
|
|
1964
1968
|
FE_unit = FE_unit,
|
|
1965
1969
|
FE_time = FE_time,
|
|
1966
1970
|
cluster_SE_by = cluster_SE_by,
|
|
@@ -1994,7 +1998,7 @@ def merge_data(
|
|
|
1994
1998
|
drop_missing: bool = True,
|
|
1995
1999
|
missing_replace_by_zero: bool = False,
|
|
1996
2000
|
keep_columns: bool = False,
|
|
1997
|
-
verbose: bool =
|
|
2001
|
+
verbose: bool = False
|
|
1998
2002
|
):
|
|
1999
2003
|
|
|
2000
2004
|
"""
|
|
@@ -2186,7 +2190,7 @@ def create_data(
|
|
|
2186
2190
|
after_treatment_period: bool = False,
|
|
2187
2191
|
drop_missing: bool = True,
|
|
2188
2192
|
missing_replace_by_zero: bool = False,
|
|
2189
|
-
verbose: bool =
|
|
2193
|
+
verbose: bool = False
|
|
2190
2194
|
):
|
|
2191
2195
|
|
|
2192
2196
|
"""
|
|
@@ -2225,7 +2229,7 @@ def create_data(
|
|
|
2225
2229
|
missing_replace_by_zero : bool, optional
|
|
2226
2230
|
If True, replace missing values by zero when requested. Default is False.
|
|
2227
2231
|
verbose : bool, optional
|
|
2228
|
-
If True, print progress messages.
|
|
2232
|
+
If True, print progress messages.
|
|
2229
2233
|
|
|
2230
2234
|
Returns
|
|
2231
2235
|
-------
|
|
@@ -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.2.
|
|
8
|
-
# Last update: 2026-
|
|
7
|
+
# Version: 2.2.1
|
|
8
|
+
# Last update: 2026-03-03 17:34
|
|
9
9
|
# Copyright (c) 2025-2026 Thomas Wieland
|
|
10
10
|
#-----------------------------------------------------------------------
|
|
11
11
|
|
|
@@ -434,7 +434,7 @@ def is_missing(
|
|
|
434
434
|
if len(missing_true_vars) > 0:
|
|
435
435
|
print(f"WARNING: Data frame contains columns with missing values: {', '.join(missing_true_vars)}.")
|
|
436
436
|
|
|
437
|
-
if drop_missing and not missing_replace_by_zero:
|
|
437
|
+
if drop_missing and not missing_replace_by_zero and len(missing_true_vars) > 0:
|
|
438
438
|
|
|
439
439
|
if verbose:
|
|
440
440
|
print("Dropping rows with missing values", end = " ... ")
|
|
@@ -521,9 +521,16 @@ def is_simultaneous(
|
|
|
521
521
|
treatment_group = data_isnotreatment[1]
|
|
522
522
|
data_TG = data[data[unit_col].isin(treatment_group)]
|
|
523
523
|
|
|
524
|
-
data_TG_pivot = data_TG.pivot_table
|
|
524
|
+
data_TG_pivot = data_TG.pivot_table(
|
|
525
|
+
index = time_col,
|
|
526
|
+
columns = unit_col,
|
|
527
|
+
values = treatment_col
|
|
528
|
+
)
|
|
525
529
|
|
|
526
|
-
|
|
530
|
+
if config.ACCEPT_CONTINUOUS_TREATMENTS:
|
|
531
|
+
simultaneous = (data_TG_pivot.nunique(axis=1) > 0).all()
|
|
532
|
+
else:
|
|
533
|
+
simultaneous = (data_TG_pivot.nunique(axis=1) == 1).all()
|
|
527
534
|
|
|
528
535
|
if verbose:
|
|
529
536
|
print("OK")
|
|
@@ -825,34 +832,67 @@ def is_parallel(
|
|
|
825
832
|
|
|
826
833
|
treatment_group = modeldata_isnotreatment[1]
|
|
827
834
|
|
|
828
|
-
if
|
|
835
|
+
if config.ACCEPT_CONTINUOUS_TREATMENTS:
|
|
829
836
|
|
|
830
|
-
|
|
837
|
+
if len(data[(data[unit_col].isin(treatment_group)) & (data[treatment_col] > 0)]) > 0:
|
|
831
838
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
839
|
+
first_day_of_treatment = min(data[(data[unit_col].isin(treatment_group)) & (data[treatment_col] > 0)][time_col])
|
|
840
|
+
|
|
841
|
+
data_test = data[data[time_col] < first_day_of_treatment].copy()
|
|
842
|
+
data_test[config.TG_COL] = 0
|
|
843
|
+
data_test.loc[data_test[unit_col].isin(treatment_group), config.TG_COL] = 1
|
|
844
|
+
|
|
845
|
+
if config.TIME_COUNTER_COL not in data_test.columns:
|
|
846
|
+
data_test = date_counter(
|
|
847
|
+
df = data_test,
|
|
848
|
+
date_col = time_col,
|
|
849
|
+
new_col = config.TIME_COUNTER_COL,
|
|
850
|
+
verbose = False
|
|
851
|
+
)
|
|
852
|
+
data_test[f"{config.TG_COL}_x_{config.TIME_COL}"] = data_test[config.TG_COL]*data_test[config.TIME_COUNTER_COL]
|
|
853
|
+
|
|
854
|
+
test_ols_model = ols(f'{outcome_col} ~ {config.TG_COL} + {config.TIME_COUNTER_COL} + {config.TG_COL}_x_{config.TIME_COL}', data = data_test).fit()
|
|
855
|
+
coef_TG_x_t_p = test_ols_model.pvalues[f"{config.TG_COL}_x_{config.TIME_COL}"]
|
|
856
|
+
|
|
857
|
+
if coef_TG_x_t_p < alpha:
|
|
858
|
+
parallel = False
|
|
859
|
+
else:
|
|
860
|
+
parallel = True
|
|
835
861
|
|
|
836
|
-
if config.TIME_COUNTER_COL not in data_test.columns:
|
|
837
|
-
data_test = date_counter(
|
|
838
|
-
df = data_test,
|
|
839
|
-
date_col = time_col,
|
|
840
|
-
new_col = config.TIME_COUNTER_COL,
|
|
841
|
-
verbose = False
|
|
842
|
-
)
|
|
843
|
-
data_test[f"{config.TG_COL}_x_{config.TIME_COL}"] = data_test[config.TG_COL]*data_test[config.TIME_COUNTER_COL]
|
|
844
|
-
|
|
845
|
-
test_ols_model = ols(f'{outcome_col} ~ {config.TG_COL} + {config.TIME_COUNTER_COL} + {config.TG_COL}_x_{config.TIME_COL}', data = data_test).fit()
|
|
846
|
-
coef_TG_x_t_p = test_ols_model.pvalues[f"{config.TG_COL}_x_{config.TIME_COL}"]
|
|
847
|
-
|
|
848
|
-
if coef_TG_x_t_p < alpha:
|
|
849
|
-
parallel = False
|
|
850
862
|
else:
|
|
851
|
-
parallel =
|
|
863
|
+
parallel = "not_tested"
|
|
864
|
+
test_ols_model = None
|
|
852
865
|
|
|
853
866
|
else:
|
|
854
|
-
|
|
855
|
-
|
|
867
|
+
|
|
868
|
+
if len(data[(data[unit_col].isin(treatment_group)) & (data[treatment_col] == 1)]) > 0:
|
|
869
|
+
|
|
870
|
+
first_day_of_treatment = min(data[(data[unit_col].isin(treatment_group)) & (data[treatment_col] == 1)][time_col])
|
|
871
|
+
|
|
872
|
+
data_test = data[data[time_col] < first_day_of_treatment].copy()
|
|
873
|
+
data_test[config.TG_COL] = 0
|
|
874
|
+
data_test.loc[data_test[unit_col].isin(treatment_group), config.TG_COL] = 1
|
|
875
|
+
|
|
876
|
+
if config.TIME_COUNTER_COL not in data_test.columns:
|
|
877
|
+
data_test = date_counter(
|
|
878
|
+
df = data_test,
|
|
879
|
+
date_col = time_col,
|
|
880
|
+
new_col = config.TIME_COUNTER_COL,
|
|
881
|
+
verbose = False
|
|
882
|
+
)
|
|
883
|
+
data_test[f"{config.TG_COL}_x_{config.TIME_COL}"] = data_test[config.TG_COL]*data_test[config.TIME_COUNTER_COL]
|
|
884
|
+
|
|
885
|
+
test_ols_model = ols(f'{outcome_col} ~ {config.TG_COL} + {config.TIME_COUNTER_COL} + {config.TG_COL}_x_{config.TIME_COL}', data = data_test).fit()
|
|
886
|
+
coef_TG_x_t_p = test_ols_model.pvalues[f"{config.TG_COL}_x_{config.TIME_COL}"]
|
|
887
|
+
|
|
888
|
+
if coef_TG_x_t_p < alpha:
|
|
889
|
+
parallel = False
|
|
890
|
+
else:
|
|
891
|
+
parallel = True
|
|
892
|
+
|
|
893
|
+
else:
|
|
894
|
+
parallel = "not_tested"
|
|
895
|
+
test_ols_model = None
|
|
856
896
|
|
|
857
897
|
if verbose:
|
|
858
898
|
print("OK")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: diffindiff
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.2
|
|
4
4
|
Summary: diffindiff: Python library for convenient Difference-in-Differences analyses
|
|
5
5
|
Author: Thomas Wieland
|
|
6
6
|
Author-email: geowieland@googlemail.com
|
|
@@ -27,7 +27,7 @@ Thomas Wieland [ORCID](https://orcid.org/0000-0001-5168-9846) [EMail](mailto:geo
|
|
|
27
27
|
|
|
28
28
|
If you use this software, please cite:
|
|
29
29
|
|
|
30
|
-
Wieland, T. (2026). diffindiff: A Python library for convenient difference-in-differences analyses (Version 2.3.
|
|
30
|
+
Wieland, T. (2026). diffindiff: A Python library for convenient difference-in-differences analyses (Version 2.3.2) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.18656820
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
## Installation
|
|
@@ -52,10 +52,11 @@ pip install git+https://github.com/geowieland/diffindiff_official.git
|
|
|
52
52
|
- Create ready-to-fit DiD data objects
|
|
53
53
|
- Create predictive counterfactuals
|
|
54
54
|
- **DiD analysis**:
|
|
55
|
-
-
|
|
56
|
-
-
|
|
55
|
+
- Perform standard DiD analysis with pre-post data
|
|
56
|
+
- Perform DiD analysis with two-way fixed effects models
|
|
57
57
|
- Simultaneous and/or staggered adoption are supported
|
|
58
58
|
- Single or multiple treatments are supported
|
|
59
|
+
- Binary or continuous treatments are supported
|
|
59
60
|
- Model extensions for DiD analysis:
|
|
60
61
|
- Group- or individual-specific treatment effects
|
|
61
62
|
- Group- or individual-specific time trends
|
|
@@ -66,10 +67,10 @@ pip install git+https://github.com/geowieland/diffindiff_official.git
|
|
|
66
67
|
- Add own counterfactuals or create counterfactuals based on machine learning or OLS regression models
|
|
67
68
|
- Bonferroni correction for treatment effects
|
|
68
69
|
- Placebo test
|
|
69
|
-
- Test for control conditions
|
|
70
|
-
- Test for type of adoption
|
|
71
|
-
- Test whether the panel dataset is balanced
|
|
72
|
-
- Test for parallel trend assumption
|
|
70
|
+
- Test for control conditions (automatically within analysis or stand-alone)
|
|
71
|
+
- Test for type of adoption (automatically within analysis or stand-alone)
|
|
72
|
+
- Test whether the panel dataset is balanced (automatically within analysis or stand-alone)
|
|
73
|
+
- Test for parallel trend assumption (automatically within analysis or stand-alone)
|
|
73
74
|
- **Visualization**:
|
|
74
75
|
- Plot observed and expected time course of treatment and control group
|
|
75
76
|
- Plot expected time course of treatment group and counterfactual
|
|
@@ -169,19 +170,10 @@ See the /tests directory for usage examples of most of the included functions.
|
|
|
169
170
|
|
|
170
171
|
## AI Usage Statement
|
|
171
172
|
|
|
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 `
|
|
173
|
+
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).
|
|
173
174
|
|
|
174
175
|
|
|
175
|
-
## What's new (v2.3.
|
|
176
|
+
## What's new (v2.3.2)
|
|
176
177
|
|
|
177
|
-
-
|
|
178
|
-
-
|
|
179
|
-
- Extended information in README
|
|
180
|
-
- Extensions
|
|
181
|
-
- Timestamps for all methods creating or changing DiffGroups, DiffTreatment, DiffData and DiffModel objects
|
|
182
|
-
- Hyperparameters and other config information in didtools.model_wrapper() are now saved
|
|
183
|
-
- All summary methods return self
|
|
184
|
-
- Bugfixes:
|
|
185
|
-
- Correct check of input lists in didanalysis_helper functions create_spillover() and extract_model_results()
|
|
186
|
-
- Correct check of input lists in DiffData methods add_covariates() and analysis()
|
|
187
|
-
- Correct check of input lists in didanalysis functions did_analysis() and ddd_analysis()
|
|
178
|
+
- Extensions:
|
|
179
|
+
- Re-transform log-transformed outcomes in DiffModel.plot() via parameter 'retransform_log_outcome'
|
|
@@ -7,7 +7,7 @@ def read_README():
|
|
|
7
7
|
|
|
8
8
|
setup(
|
|
9
9
|
name='diffindiff',
|
|
10
|
-
version='2.3.
|
|
10
|
+
version='2.3.2',
|
|
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
|