diff-diff 2.1.6__tar.gz → 2.1.7__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 (34) hide show
  1. {diff_diff-2.1.6 → diff_diff-2.1.7}/PKG-INFO +1 -1
  2. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/__init__.py +1 -1
  3. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/visualization.py +44 -11
  4. {diff_diff-2.1.6 → diff_diff-2.1.7}/pyproject.toml +1 -1
  5. {diff_diff-2.1.6 → diff_diff-2.1.7}/rust/Cargo.lock +3 -3
  6. {diff_diff-2.1.6 → diff_diff-2.1.7}/rust/Cargo.toml +1 -1
  7. {diff_diff-2.1.6 → diff_diff-2.1.7}/README.md +0 -0
  8. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/_backend.py +0 -0
  9. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/bacon.py +0 -0
  10. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/datasets.py +0 -0
  11. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/diagnostics.py +0 -0
  12. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/estimators.py +0 -0
  13. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/honest_did.py +0 -0
  14. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/linalg.py +0 -0
  15. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/power.py +0 -0
  16. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/prep.py +0 -0
  17. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/prep_dgp.py +0 -0
  18. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/pretrends.py +0 -0
  19. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/results.py +0 -0
  20. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/staggered.py +0 -0
  21. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/staggered_aggregation.py +0 -0
  22. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/staggered_bootstrap.py +0 -0
  23. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/staggered_results.py +0 -0
  24. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/sun_abraham.py +0 -0
  25. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/synthetic_did.py +0 -0
  26. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/triple_diff.py +0 -0
  27. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/trop.py +0 -0
  28. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/twfe.py +0 -0
  29. {diff_diff-2.1.6 → diff_diff-2.1.7}/diff_diff/utils.py +0 -0
  30. {diff_diff-2.1.6 → diff_diff-2.1.7}/rust/src/bootstrap.rs +0 -0
  31. {diff_diff-2.1.6 → diff_diff-2.1.7}/rust/src/lib.rs +0 -0
  32. {diff_diff-2.1.6 → diff_diff-2.1.7}/rust/src/linalg.rs +0 -0
  33. {diff_diff-2.1.6 → diff_diff-2.1.7}/rust/src/trop.rs +0 -0
  34. {diff_diff-2.1.6 → diff_diff-2.1.7}/rust/src/weights.rs +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diff-diff
3
- Version: 2.1.6
3
+ Version: 2.1.7
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Intended Audience :: Science/Research
6
6
  Classifier: Operating System :: OS Independent
@@ -136,7 +136,7 @@ from diff_diff.datasets import (
136
136
  load_mpdta,
137
137
  )
138
138
 
139
- __version__ = "2.1.6"
139
+ __version__ = "2.1.7"
140
140
  __all__ = [
141
141
  # Estimators
142
142
  "DifferenceInDifferences",
@@ -73,8 +73,10 @@ def plot_event_study(
73
73
  periods : list, optional
74
74
  List of periods to plot. If None, uses all periods from results.
75
75
  reference_period : any, optional
76
- The reference period (normalized to effect=0). Will be shown as a
77
- hollow marker. If None, tries to infer from results.
76
+ The reference period to highlight. When explicitly provided, effects
77
+ are normalized (ref effect subtracted) and ref SE is set to NaN.
78
+ When None and auto-inferred from results, only hollow marker styling
79
+ is applied (no normalization). If None, tries to infer from results.
78
80
  pre_periods : list, optional
79
81
  List of pre-treatment periods. Used for shading.
80
82
  post_periods : list, optional
@@ -151,8 +153,9 @@ def plot_event_study(
151
153
  trends holds. Large pre-treatment effects suggest the assumption may
152
154
  be violated.
153
155
 
154
- 2. **Reference period**: Usually the last pre-treatment period (t=-1),
155
- normalized to zero. This is the omitted category.
156
+ 2. **Reference period**: Usually the last pre-treatment period (t=-1).
157
+ When explicitly specified via ``reference_period``, effects are normalized
158
+ to zero at this period. When auto-inferred, shown with hollow marker only.
156
159
 
157
160
  3. **Post-treatment periods**: The treatment effects of interest. These
158
161
  show how the outcome evolved after treatment.
@@ -170,10 +173,18 @@ def plot_event_study(
170
173
 
171
174
  from scipy import stats as scipy_stats
172
175
 
176
+ # Track if reference_period was explicitly provided by user
177
+ reference_period_explicit = reference_period is not None
178
+
173
179
  # Extract data from results if provided
174
180
  if results is not None:
175
- effects, se, periods, pre_periods, post_periods, reference_period = \
176
- _extract_plot_data(results, periods, pre_periods, post_periods, reference_period)
181
+ extracted = _extract_plot_data(
182
+ results, periods, pre_periods, post_periods, reference_period
183
+ )
184
+ effects, se, periods, pre_periods, post_periods, reference_period, reference_inferred = extracted
185
+ # If reference was inferred from results, it was NOT explicitly provided
186
+ if reference_inferred:
187
+ reference_period_explicit = False
177
188
  elif effects is None or se is None:
178
189
  raise ValueError(
179
190
  "Must provide either 'results' or both 'effects' and 'se'"
@@ -192,6 +203,19 @@ def plot_event_study(
192
203
  # Compute confidence intervals
193
204
  critical_value = scipy_stats.norm.ppf(1 - alpha / 2)
194
205
 
206
+ # Normalize effects to reference period ONLY if explicitly specified by user
207
+ # Auto-inferred reference periods (from CallawaySantAnna) just get hollow marker styling,
208
+ # NO normalization. This prevents unintended normalization when the reference period
209
+ # isn't a true identifying constraint (e.g., CallawaySantAnna with base_period="varying").
210
+ if (reference_period is not None and reference_period in effects and
211
+ reference_period_explicit):
212
+ ref_effect = effects[reference_period]
213
+ if np.isfinite(ref_effect):
214
+ effects = {p: e - ref_effect for p, e in effects.items()}
215
+ # Set reference SE to NaN (it's now a constraint, not an estimate)
216
+ # This follows fixest convention where the omitted category has no SE/CI
217
+ se = {p: (np.nan if p == reference_period else s) for p, s in se.items()}
218
+
195
219
  plot_data = []
196
220
  for period in periods:
197
221
  effect = effects.get(period, np.nan)
@@ -304,14 +328,17 @@ def _extract_plot_data(
304
328
  pre_periods: Optional[List[Any]],
305
329
  post_periods: Optional[List[Any]],
306
330
  reference_period: Optional[Any],
307
- ) -> Tuple[Dict, Dict, List, List, List, Any]:
331
+ ) -> Tuple[Dict, Dict, List, List, List, Any, bool]:
308
332
  """
309
333
  Extract plotting data from various result types.
310
334
 
311
335
  Returns
312
336
  -------
313
337
  tuple
314
- (effects, se, periods, pre_periods, post_periods, reference_period)
338
+ (effects, se, periods, pre_periods, post_periods, reference_period, reference_inferred)
339
+
340
+ reference_inferred is True if reference_period was auto-detected from results
341
+ rather than explicitly provided by the user.
315
342
  """
316
343
  # Handle DataFrame input
317
344
  if isinstance(results, pd.DataFrame):
@@ -328,7 +355,8 @@ def _extract_plot_data(
328
355
  if periods is None:
329
356
  periods = list(results['period'])
330
357
 
331
- return effects, se, periods, pre_periods, post_periods, reference_period
358
+ # DataFrame input: reference_period was already set by caller, never inferred here
359
+ return effects, se, periods, pre_periods, post_periods, reference_period, False
332
360
 
333
361
  # Handle MultiPeriodDiDResults
334
362
  if hasattr(results, 'period_effects'):
@@ -348,7 +376,8 @@ def _extract_plot_data(
348
376
  if periods is None:
349
377
  periods = post_periods
350
378
 
351
- return effects, se, periods, pre_periods, post_periods, reference_period
379
+ # MultiPeriodDiDResults: reference_period was already set by caller, never inferred here
380
+ return effects, se, periods, pre_periods, post_periods, reference_period, False
352
381
 
353
382
  # Handle CallawaySantAnnaResults (event study aggregation)
354
383
  if hasattr(results, 'event_study_effects') and results.event_study_effects is not None:
@@ -362,8 +391,12 @@ def _extract_plot_data(
362
391
  if periods is None:
363
392
  periods = sorted(effects.keys())
364
393
 
394
+ # Track if reference_period was explicitly provided vs auto-inferred
395
+ reference_inferred = False
396
+
365
397
  # Reference period is typically -1 for event study
366
398
  if reference_period is None:
399
+ reference_inferred = True # We're about to infer it
367
400
  # Detect reference period from n_groups=0 marker (normalization constraint)
368
401
  # This handles anticipation > 0 where reference is at e = -1 - anticipation
369
402
  for period, effect_data in results.event_study_effects.items():
@@ -380,7 +413,7 @@ def _extract_plot_data(
380
413
  if post_periods is None:
381
414
  post_periods = [p for p in periods if p >= 0]
382
415
 
383
- return effects, se, periods, pre_periods, post_periods, reference_period
416
+ return effects, se, periods, pre_periods, post_periods, reference_period, reference_inferred
384
417
 
385
418
  raise TypeError(
386
419
  f"Cannot extract plot data from {type(results).__name__}. "
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "diff-diff"
7
- version = "2.1.6"
7
+ version = "2.1.7"
8
8
  description = "A library for Difference-in-Differences causal inference analysis"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -289,7 +289,7 @@ dependencies = [
289
289
 
290
290
  [[package]]
291
291
  name = "diff_diff_rust"
292
- version = "2.1.6"
292
+ version = "2.1.7"
293
293
  dependencies = [
294
294
  "ndarray",
295
295
  "ndarray-linalg",
@@ -1846,9 +1846,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
1846
1846
 
1847
1847
  [[package]]
1848
1848
  name = "uuid"
1849
- version = "1.19.0"
1849
+ version = "1.20.0"
1850
1850
  source = "registry+https://github.com/rust-lang/crates.io-index"
1851
- checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
1851
+ checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
1852
1852
  dependencies = [
1853
1853
  "getrandom 0.3.4",
1854
1854
  "js-sys",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "diff_diff_rust"
3
- version = "2.1.6"
3
+ version = "2.1.7"
4
4
  edition = "2021"
5
5
  description = "Rust backend for diff-diff DiD library"
6
6
  license = "MIT"
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