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.
Files changed (21) hide show
  1. {diffindiff-2.3.0 → diffindiff-2.3.2}/PKG-INFO +13 -21
  2. {diffindiff-2.3.0 → diffindiff-2.3.2}/README.md +12 -20
  3. {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/config.py +3 -3
  4. {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/didanalysis.py +80 -32
  5. {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/diddata.py +36 -32
  6. {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/didtools.py +67 -27
  7. {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff.egg-info/PKG-INFO +13 -21
  8. {diffindiff-2.3.0 → diffindiff-2.3.2}/setup.py +1 -1
  9. {diffindiff-2.3.0 → diffindiff-2.3.2}/MANIFEST.in +0 -0
  10. {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/__init__.py +0 -0
  11. {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/didanalysis_helper.py +0 -0
  12. {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/tests/__init__.py +0 -0
  13. {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/tests/data/Corona_Hesse.xlsx +0 -0
  14. {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/tests/data/counties_DE.csv +0 -0
  15. {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/tests/data/curfew_DE.csv +0 -0
  16. {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff/tests/tests_diffindiff.py +0 -0
  17. {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff.egg-info/SOURCES.txt +0 -0
  18. {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff.egg-info/dependency_links.txt +0 -0
  19. {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff.egg-info/requires.txt +0 -0
  20. {diffindiff-2.3.0 → diffindiff-2.3.2}/diffindiff.egg-info/top_level.txt +0 -0
  21. {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.0
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.0) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.18656820
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
- - Perfom standard DiD analysis with pre-post data
56
- - Perfom DiD analysis with two-way fixed effects models
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 `Agents.md` in the [public GitHub repository](https://github.com/geowieland/diffindiff_official).
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.0)
176
+ ## What's new (v2.3.2)
176
177
 
177
- - General
178
- - Full documentation (docstring in NumPy style) of all classes, methods, and functions
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.0) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.18656820
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
- - Perfom standard DiD analysis with pre-post data
48
- - Perfom DiD analysis with two-way fixed effects models
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 `Agents.md` in the [public GitHub repository](https://github.com/geowieland/diffindiff_official).
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.0)
168
+ ## What's new (v2.3.2)
168
169
 
169
- - General
170
- - Full documentation (docstring in NumPy style) of all classes, methods, and functions
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
8
- # Last update: 2026-02-27 21:55
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.0"
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.0
8
- # Last update: 2026-03-01 11:23
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 len(model_data[model_data[treatment] == 1]) > 0:
190
- treatment_period_start = pd.to_datetime(min(model_data[model_data[treatment] == 1][time_col]))
191
- treatment_period_end = pd.to_datetime(max(model_data[model_data[treatment] == 1][time_col]))
192
- treatment_period_N = model_data.loc[model_data[treatment] == 1, time_col].nunique()
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
- treatment_period_N = 0
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
- after_treatment_period_N = model_data.loc[model_data[after_treatment_col] == 1, time_col].nunique()
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
- time_points_treatment = modeldata_pivot.index[modeldata_pivot[col] == 1]
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
- FE_unit = True
2273
+ if no_treatments > 1:
2274
+
2235
2275
  intercept = False
2236
- TG_col = []
2237
- print("NOTE: Quasi-experiment includes more than one treatment. Unit fixed effects are used instead of control group baseline and treatment group deviation.")
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
- FE_unit = True
2242
- print("NOTE: Model includes individual treatment effects. Unit fixed effects are included.")
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
- print("NOTE: Model includes individual time trends. Unit fixed effects are included. Treatment time variable is dropped.")
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.0
8
- # Last update: 2026-03-01 20:04
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 = config.VERBOSE
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 = config.VERBOSE
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 = config.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 = config.VERBOSE
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 = config.VERBOSE
1392
+ verbose: bool = False
1393
1393
  ):
1394
1394
 
1395
1395
  """
@@ -1420,25 +1420,25 @@ class DiffData:
1420
1420
 
1421
1421
  Examples
1422
1422
  --------
1423
- >>> Sensoria_Daten=did.create_data(
1424
- ... outcome_data=Tourismusdaten_Tabelle,
1425
- ... unit_id_col="Gemeinde",
1426
- ... time_col="Jahr_Monat",
1427
- ... outcome_col="Gaesteuebernachtungen_insgesamt",
1428
- ... treatment_group=Tourismusdaten_Tabelle.loc[Tourismusdaten_Tabelle["Gemeinde"] == "255023 Holzminden,Stadt"]["Gemeinde"],
1429
- ... control_group=Tourismusdaten_Tabelle.loc[Tourismusdaten_Tabelle["Gemeinde"] != "255023 Holzminden,Stadt"]["Gemeinde"],
1430
- ... study_period=["2023-01-01", "2025-10-01"],
1431
- ... treatment_period=["2024-09-01", "2025-10-01"],
1432
- ... freq="MS",
1433
- ... treatment_name="Sensoria_geoeffnet",
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
- >>> Sensoria_Daten.add_covariates(
1436
- ... additional_df=Tourismusdaten_Tabelle,
1437
- ... variables=["Schlafgelegenheitentage_angeboten", "Event_Tage"],
1438
- ... unit_col = "Gemeinde",
1439
- ... time_col="Jahr_Monat",
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
- >>> Sensoria_Daten.define_treatment("Event_Tage")
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 = config.VERBOSE
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 = config.VERBOSE
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 = 0.01,
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 = config.VERBOSE
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 = config.VERBOSE
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. Default follows package config.
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.0
8
- # Last update: 2026-02-28 21:47
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 (index = time_col, columns = unit_col, values = treatment_col)
524
+ data_TG_pivot = data_TG.pivot_table(
525
+ index = time_col,
526
+ columns = unit_col,
527
+ values = treatment_col
528
+ )
525
529
 
526
- simultaneous = (data_TG_pivot.nunique(axis=1) == 1).all()
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 len(data[(data[unit_col].isin(treatment_group)) & (data[treatment_col] == 1)]) > 0:
835
+ if config.ACCEPT_CONTINUOUS_TREATMENTS:
829
836
 
830
- first_day_of_treatment = min(data[(data[unit_col].isin(treatment_group)) & (data[treatment_col] == 1)][time_col])
837
+ if len(data[(data[unit_col].isin(treatment_group)) & (data[treatment_col] > 0)]) > 0:
831
838
 
832
- data_test = data[data[time_col] < first_day_of_treatment].copy()
833
- data_test[config.TG_COL] = 0
834
- data_test.loc[data_test[unit_col].isin(treatment_group), config.TG_COL] = 1
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 = True
863
+ parallel = "not_tested"
864
+ test_ols_model = None
852
865
 
853
866
  else:
854
- parallel = "not_tested"
855
- test_ols_model = None
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.0
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.0) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.18656820
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
- - Perfom standard DiD analysis with pre-post data
56
- - Perfom DiD analysis with two-way fixed effects models
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 `Agents.md` in the [public GitHub repository](https://github.com/geowieland/diffindiff_official).
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.0)
176
+ ## What's new (v2.3.2)
176
177
 
177
- - General
178
- - Full documentation (docstring in NumPy style) of all classes, methods, and functions
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.0',
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