diff-diff 3.1.0__tar.gz → 3.1.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.
Files changed (71) hide show
  1. {diff_diff-3.1.0 → diff_diff-3.1.1}/PKG-INFO +1 -1
  2. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/__init__.py +1 -1
  3. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/results.py +7 -2
  4. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/synthetic_did.py +233 -9
  5. {diff_diff-3.1.0 → diff_diff-3.1.1}/pyproject.toml +1 -1
  6. {diff_diff-3.1.0 → diff_diff-3.1.1}/rust/Cargo.lock +1 -1
  7. {diff_diff-3.1.0 → diff_diff-3.1.1}/rust/Cargo.toml +1 -1
  8. {diff_diff-3.1.0 → diff_diff-3.1.1}/README.md +0 -0
  9. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/_backend.py +0 -0
  10. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/bacon.py +0 -0
  11. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/bootstrap_utils.py +0 -0
  12. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/chaisemartin_dhaultfoeuille.py +0 -0
  13. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/chaisemartin_dhaultfoeuille_bootstrap.py +0 -0
  14. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/chaisemartin_dhaultfoeuille_results.py +0 -0
  15. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/continuous_did.py +0 -0
  16. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/continuous_did_bspline.py +0 -0
  17. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/continuous_did_results.py +0 -0
  18. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/datasets.py +0 -0
  19. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/diagnostics.py +0 -0
  20. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/efficient_did.py +0 -0
  21. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/efficient_did_bootstrap.py +0 -0
  22. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/efficient_did_covariates.py +0 -0
  23. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/efficient_did_results.py +0 -0
  24. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/efficient_did_weights.py +0 -0
  25. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/estimators.py +0 -0
  26. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/honest_did.py +0 -0
  27. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/imputation.py +0 -0
  28. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/imputation_bootstrap.py +0 -0
  29. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/imputation_results.py +0 -0
  30. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/linalg.py +0 -0
  31. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/power.py +0 -0
  32. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/practitioner.py +0 -0
  33. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/prep.py +0 -0
  34. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/prep_dgp.py +0 -0
  35. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/pretrends.py +0 -0
  36. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/stacked_did.py +0 -0
  37. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/stacked_did_results.py +0 -0
  38. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/staggered.py +0 -0
  39. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/staggered_aggregation.py +0 -0
  40. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/staggered_bootstrap.py +0 -0
  41. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/staggered_results.py +0 -0
  42. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/staggered_triple_diff.py +0 -0
  43. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/staggered_triple_diff_results.py +0 -0
  44. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/sun_abraham.py +0 -0
  45. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/survey.py +0 -0
  46. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/triple_diff.py +0 -0
  47. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/trop.py +0 -0
  48. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/trop_global.py +0 -0
  49. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/trop_local.py +0 -0
  50. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/trop_results.py +0 -0
  51. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/twfe.py +0 -0
  52. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/two_stage.py +0 -0
  53. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/two_stage_bootstrap.py +0 -0
  54. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/two_stage_results.py +0 -0
  55. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/utils.py +0 -0
  56. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/visualization/__init__.py +0 -0
  57. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/visualization/_common.py +0 -0
  58. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/visualization/_continuous.py +0 -0
  59. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/visualization/_diagnostic.py +0 -0
  60. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/visualization/_event_study.py +0 -0
  61. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/visualization/_power.py +0 -0
  62. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/visualization/_staggered.py +0 -0
  63. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/visualization/_synthetic.py +0 -0
  64. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/wooldridge.py +0 -0
  65. {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/wooldridge_results.py +0 -0
  66. {diff_diff-3.1.0 → diff_diff-3.1.1}/rust/build.rs +0 -0
  67. {diff_diff-3.1.0 → diff_diff-3.1.1}/rust/src/bootstrap.rs +0 -0
  68. {diff_diff-3.1.0 → diff_diff-3.1.1}/rust/src/lib.rs +0 -0
  69. {diff_diff-3.1.0 → diff_diff-3.1.1}/rust/src/linalg.rs +0 -0
  70. {diff_diff-3.1.0 → diff_diff-3.1.1}/rust/src/trop.rs +0 -0
  71. {diff_diff-3.1.0 → diff_diff-3.1.1}/rust/src/weights.rs +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diff-diff
3
- Version: 3.1.0
3
+ Version: 3.1.1
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Intended Audience :: Science/Research
6
6
  Classifier: Operating System :: OS Independent
@@ -228,7 +228,7 @@ EDiD = EfficientDiD
228
228
  ETWFE = WooldridgeDiD
229
229
  DCDH = ChaisemartinDHaultfoeuille
230
230
 
231
- __version__ = "3.1.0"
231
+ __version__ = "3.1.1"
232
232
  __all__ = [
233
233
  # Estimators
234
234
  "DifferenceInDifferences",
@@ -662,7 +662,7 @@ class SyntheticDiDResults:
662
662
  att : float
663
663
  Average Treatment effect on the Treated (ATT).
664
664
  se : float
665
- Standard error of the ATT estimate (bootstrap or placebo-based).
665
+ Standard error of the ATT estimate (bootstrap, jackknife, or placebo-based).
666
666
  t_stat : float
667
667
  T-statistic for the ATT estimate.
668
668
  p_value : float
@@ -684,7 +684,12 @@ class SyntheticDiDResults:
684
684
  post_periods : list
685
685
  List of post-treatment period identifiers.
686
686
  variance_method : str
687
- Method used for variance estimation: "bootstrap" or "placebo".
687
+ Method used for variance estimation: "bootstrap", "jackknife", or "placebo".
688
+ placebo_effects : np.ndarray, optional
689
+ Method-specific per-iteration estimates: placebo treatment effects
690
+ (for "placebo"), bootstrap ATT estimates (for "bootstrap"), or
691
+ leave-one-out estimates (for "jackknife"). The ``variance_method``
692
+ field disambiguates the contents.
688
693
  """
689
694
 
690
695
  att: float
@@ -53,10 +53,15 @@ class SyntheticDiD(DifferenceInDifferences):
53
53
  Implements Algorithm 4 from Arkhangelsky et al. (2021). This is R's default.
54
54
  - "bootstrap": Bootstrap at unit level with fixed weights matching R's
55
55
  synthdid::vcov(method="bootstrap").
56
+ - "jackknife": Jackknife variance matching R's synthdid::vcov(method="jackknife").
57
+ Implements Algorithm 3 from Arkhangelsky et al. (2021). Deterministic
58
+ (N_control + N_treated iterations), uses fixed weights (no re-estimation).
59
+ The ``n_bootstrap`` parameter is ignored for this method.
56
60
  n_bootstrap : int, default=200
57
61
  Number of replications for variance estimation. Used for both:
58
62
  - Bootstrap: Number of bootstrap samples
59
63
  - Placebo: Number of random permutations (matches R's `replications` argument)
64
+ Ignored when ``variance_method="jackknife"``.
60
65
  seed : int, optional
61
66
  Random seed for reproducibility. If None (default), results
62
67
  will vary between runs.
@@ -163,15 +168,15 @@ class SyntheticDiD(DifferenceInDifferences):
163
168
  self.n_bootstrap = n_bootstrap
164
169
  self.seed = seed
165
170
 
166
- # Validate n_bootstrap
167
- if n_bootstrap < 2:
171
+ # Validate n_bootstrap (irrelevant for jackknife, which is deterministic)
172
+ if n_bootstrap < 2 and variance_method != "jackknife":
168
173
  raise ValueError(
169
174
  f"n_bootstrap must be >= 2 (got {n_bootstrap}). At least 2 "
170
175
  f"iterations are needed to estimate standard errors."
171
176
  )
172
177
 
173
178
  # Validate variance_method
174
- valid_methods = ("bootstrap", "placebo")
179
+ valid_methods = ("bootstrap", "jackknife", "placebo")
175
180
  if variance_method not in valid_methods:
176
181
  raise ValueError(
177
182
  f"variance_method must be one of {valid_methods}, " f"got '{variance_method}'"
@@ -218,8 +223,9 @@ class SyntheticDiD(DifferenceInDifferences):
218
223
  survey_design : SurveyDesign, optional
219
224
  Survey design specification. Only pweight weight_type is supported.
220
225
  Strata/PSU/FPC are supported via Rao-Wu rescaled bootstrap when
221
- variance_method='bootstrap'. Placebo variance does not support
222
- strata/PSU/FPC; use variance_method='bootstrap' for full designs.
226
+ variance_method='bootstrap'. Non-bootstrap variance methods
227
+ (placebo, jackknife) do not support strata/PSU/FPC; use
228
+ variance_method='bootstrap' for full designs.
223
229
 
224
230
  Returns
225
231
  -------
@@ -269,7 +275,7 @@ class SyntheticDiD(DifferenceInDifferences):
269
275
  f"Got '{resolved_survey.weight_type}'."
270
276
  )
271
277
 
272
- # Reject placebo + full survey design (strata/PSU/FPC are silently ignored)
278
+ # Reject non-bootstrap + full survey design (strata/PSU/FPC need Rao-Wu)
273
279
  if (
274
280
  resolved_survey is not None
275
281
  and (
@@ -277,10 +283,11 @@ class SyntheticDiD(DifferenceInDifferences):
277
283
  or resolved_survey.psu is not None
278
284
  or resolved_survey.fpc is not None
279
285
  )
280
- and self.variance_method == "placebo"
286
+ and self.variance_method != "bootstrap"
281
287
  ):
282
288
  raise NotImplementedError(
283
- "SyntheticDiD with variance_method='placebo' does not support strata/PSU/FPC. "
289
+ f"SyntheticDiD with variance_method='{self.variance_method}' does not "
290
+ "support strata/PSU/FPC. "
284
291
  "Use variance_method='bootstrap' for full survey design support."
285
292
  )
286
293
 
@@ -510,6 +517,20 @@ class SyntheticDiD(DifferenceInDifferences):
510
517
  )
511
518
  placebo_effects = bootstrap_estimates
512
519
  inference_method = "bootstrap"
520
+ elif self.variance_method == "jackknife":
521
+ # Fixed-weight jackknife (R's synthdid Algorithm 3)
522
+ se, jackknife_estimates = self._jackknife_se(
523
+ Y_pre_control,
524
+ Y_post_control,
525
+ Y_pre_treated,
526
+ Y_post_treated,
527
+ unit_weights,
528
+ time_weights,
529
+ w_treated=w_treated,
530
+ w_control=w_control,
531
+ )
532
+ placebo_effects = jackknife_estimates
533
+ inference_method = "jackknife"
513
534
  else:
514
535
  # Use placebo-based variance (R's synthdid Algorithm 4)
515
536
  se, placebo_effects = self._placebo_variance_se(
@@ -528,7 +549,14 @@ class SyntheticDiD(DifferenceInDifferences):
528
549
 
529
550
  # Compute test statistics
530
551
  t_stat, p_value_analytical, conf_int = safe_inference(att, se, alpha=self.alpha)
531
- if len(placebo_effects) > 0 and np.isfinite(t_stat):
552
+ # Empirical p-value for placebo/bootstrap (null-distribution draws).
553
+ # Jackknife pseudo-values are NOT null-distribution draws, so use
554
+ # analytical (normal) p-value instead.
555
+ if (
556
+ inference_method != "jackknife"
557
+ and len(placebo_effects) > 0
558
+ and np.isfinite(t_stat)
559
+ ):
532
560
  p_value = max(
533
561
  np.mean(np.abs(placebo_effects) >= np.abs(att)),
534
562
  1.0 / (len(placebo_effects) + 1),
@@ -1106,6 +1134,202 @@ class SyntheticDiD(DifferenceInDifferences):
1106
1134
 
1107
1135
  return se, placebo_estimates
1108
1136
 
1137
+ def _jackknife_se(
1138
+ self,
1139
+ Y_pre_control: np.ndarray,
1140
+ Y_post_control: np.ndarray,
1141
+ Y_pre_treated: np.ndarray,
1142
+ Y_post_treated: np.ndarray,
1143
+ unit_weights: np.ndarray,
1144
+ time_weights: np.ndarray,
1145
+ w_treated=None,
1146
+ w_control=None,
1147
+ ) -> Tuple[float, np.ndarray]:
1148
+ """Compute jackknife standard error matching R's synthdid Algorithm 3.
1149
+
1150
+ Delete-1 jackknife over all units (control + treated) with **fixed**
1151
+ weights. For each leave-one-out sample the original omega is subsetted
1152
+ and renormalized; lambda stays unchanged. No Frank-Wolfe
1153
+ re-estimation, making this the fastest variance method.
1154
+
1155
+ This matches R's ``synthdid::vcov(method="jackknife")`` which sets
1156
+ ``update.omega=FALSE, update.lambda=FALSE``.
1157
+
1158
+ Parameters
1159
+ ----------
1160
+ Y_pre_control : np.ndarray
1161
+ Control outcomes in pre-treatment periods, shape (n_pre, n_control).
1162
+ Y_post_control : np.ndarray
1163
+ Control outcomes in post-treatment periods, shape (n_post, n_control).
1164
+ Y_pre_treated : np.ndarray
1165
+ Treated outcomes in pre-treatment periods, shape (n_pre, n_treated).
1166
+ Y_post_treated : np.ndarray
1167
+ Treated outcomes in post-treatment periods, shape (n_post, n_treated).
1168
+ unit_weights : np.ndarray
1169
+ Unit weights from Frank-Wolfe optimization, shape (n_control,).
1170
+ time_weights : np.ndarray
1171
+ Time weights from Frank-Wolfe optimization, shape (n_pre,).
1172
+ w_treated : np.ndarray, optional
1173
+ Survey probability weights for treated units.
1174
+ w_control : np.ndarray, optional
1175
+ Survey probability weights for control units.
1176
+
1177
+ Returns
1178
+ -------
1179
+ tuple
1180
+ (se, jackknife_estimates) where se is the standard error and
1181
+ jackknife_estimates is a length-N array of leave-one-out estimates
1182
+ (first n_control entries are control-LOO, last n_treated are
1183
+ treated-LOO).
1184
+
1185
+ References
1186
+ ----------
1187
+ Arkhangelsky, D., Athey, S., Hirshberg, D. A., Imbens, G. W., & Wager, S.
1188
+ (2021). Synthetic Difference-in-Differences. American Economic Review,
1189
+ 111(12), 4088-4118. Algorithm 3.
1190
+ """
1191
+ n_control = Y_pre_control.shape[1]
1192
+ n_treated = Y_pre_treated.shape[1]
1193
+ n = n_control + n_treated
1194
+
1195
+ # --- Early-return NaN: matches R's NA conditions ---
1196
+ if n_treated <= 1:
1197
+ warnings.warn(
1198
+ "Jackknife variance requires more than 1 treated unit. "
1199
+ "Use variance_method='placebo' for single treated unit.",
1200
+ UserWarning,
1201
+ stacklevel=3,
1202
+ )
1203
+ return np.nan, np.array([])
1204
+
1205
+ if np.sum(unit_weights > 0) <= 1:
1206
+ warnings.warn(
1207
+ "Jackknife variance requires more than 1 control unit with "
1208
+ "nonzero weight. Consider variance_method='placebo'.",
1209
+ UserWarning,
1210
+ stacklevel=3,
1211
+ )
1212
+ return np.nan, np.array([])
1213
+
1214
+ # --- Effective-support guards for survey-weighted path ---
1215
+ if w_control is not None:
1216
+ effective_control = unit_weights * w_control
1217
+ if np.sum(effective_control > 0) <= 1:
1218
+ warnings.warn(
1219
+ "Jackknife variance requires more than 1 control unit with "
1220
+ "positive effective weight (omega * survey_weight). "
1221
+ "Consider variance_method='placebo'.",
1222
+ UserWarning,
1223
+ stacklevel=3,
1224
+ )
1225
+ return np.nan, np.array([])
1226
+
1227
+ if w_treated is not None and np.sum(w_treated > 0) <= 1:
1228
+ warnings.warn(
1229
+ "Jackknife variance requires more than 1 treated unit with "
1230
+ "positive survey weight. "
1231
+ "Consider variance_method='placebo'.",
1232
+ UserWarning,
1233
+ stacklevel=3,
1234
+ )
1235
+ return np.nan, np.array([])
1236
+
1237
+ jackknife_estimates = np.empty(n)
1238
+
1239
+ # --- Precompute treated means (constant across control-LOO) ---
1240
+ if w_treated is not None:
1241
+ treated_pre_mean = np.average(Y_pre_treated, axis=1, weights=w_treated)
1242
+ treated_post_mean = np.average(Y_post_treated, axis=1, weights=w_treated)
1243
+ else:
1244
+ treated_pre_mean = np.mean(Y_pre_treated, axis=1)
1245
+ treated_post_mean = np.mean(Y_post_treated, axis=1)
1246
+
1247
+ # --- Precompute omega composed with survey weights (for treated-LOO) ---
1248
+ if w_control is not None:
1249
+ omega_eff_full = unit_weights * w_control
1250
+ omega_eff_full = omega_eff_full / omega_eff_full.sum()
1251
+ else:
1252
+ omega_eff_full = unit_weights
1253
+
1254
+ # --- Leave-one-out over control units ---
1255
+ mask = np.ones(n_control, dtype=bool)
1256
+ for j in range(n_control):
1257
+ mask[j] = False
1258
+
1259
+ # Subset and renormalize omega
1260
+ omega_jk = _sum_normalize(unit_weights[mask])
1261
+
1262
+ # Compose with survey weights if present
1263
+ if w_control is not None:
1264
+ omega_jk = omega_jk * w_control[mask]
1265
+ if omega_jk.sum() == 0:
1266
+ jackknife_estimates[j] = np.nan
1267
+ mask[j] = True
1268
+ continue
1269
+ omega_jk = omega_jk / omega_jk.sum()
1270
+
1271
+ jackknife_estimates[j] = compute_sdid_estimator(
1272
+ Y_pre_control[:, mask],
1273
+ Y_post_control[:, mask],
1274
+ treated_pre_mean,
1275
+ treated_post_mean,
1276
+ omega_jk,
1277
+ time_weights,
1278
+ )
1279
+
1280
+ mask[j] = True # restore for next iteration
1281
+
1282
+ # --- Leave-one-out over treated units ---
1283
+ mask = np.ones(n_treated, dtype=bool)
1284
+ for k in range(n_treated):
1285
+ mask[k] = False
1286
+
1287
+ # Recompute treated means from remaining units
1288
+ if w_treated is not None:
1289
+ w_t_jk = w_treated[mask]
1290
+ if w_t_jk.sum() == 0:
1291
+ jackknife_estimates[n_control + k] = np.nan
1292
+ mask[k] = True
1293
+ continue
1294
+ t_pre_mean = np.average(
1295
+ Y_pre_treated[:, mask], axis=1, weights=w_t_jk
1296
+ )
1297
+ t_post_mean = np.average(
1298
+ Y_post_treated[:, mask], axis=1, weights=w_t_jk
1299
+ )
1300
+ else:
1301
+ t_pre_mean = np.mean(Y_pre_treated[:, mask], axis=1)
1302
+ t_post_mean = np.mean(Y_post_treated[:, mask], axis=1)
1303
+
1304
+ jackknife_estimates[n_control + k] = compute_sdid_estimator(
1305
+ Y_pre_control,
1306
+ Y_post_control,
1307
+ t_pre_mean,
1308
+ t_post_mean,
1309
+ omega_eff_full,
1310
+ time_weights,
1311
+ )
1312
+
1313
+ mask[k] = True # restore for next iteration
1314
+
1315
+ # --- Check for non-finite estimates (propagate NaN like R's var()) ---
1316
+ if not np.all(np.isfinite(jackknife_estimates)):
1317
+ warnings.warn(
1318
+ "Some jackknife leave-one-out estimates are non-finite. "
1319
+ "Standard error cannot be computed.",
1320
+ UserWarning,
1321
+ stacklevel=3,
1322
+ )
1323
+ return np.nan, jackknife_estimates
1324
+
1325
+ # --- Jackknife SE formula: sqrt((n-1)/n * sum((u - ubar)^2)) ---
1326
+ # Matches R's: sqrt(((n-1)/n) * (n-1) * var(u))
1327
+ u_bar = np.mean(jackknife_estimates)
1328
+ ss = np.sum((jackknife_estimates - u_bar) ** 2)
1329
+ se = np.sqrt((n - 1) / n * ss)
1330
+
1331
+ return se, jackknife_estimates
1332
+
1109
1333
  def get_params(self) -> Dict[str, Any]:
1110
1334
  """Get estimator parameters."""
1111
1335
  return {
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "diff-diff"
7
- version = "3.1.0"
7
+ version = "3.1.1"
8
8
  description = "Difference-in-Differences causal inference with sklearn-like API. Callaway-Sant'Anna, Synthetic DiD, Honest DiD, event studies, parallel trends."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -197,7 +197,7 @@ checksum = "930c7171c8df9fb1782bdf9b918ed9ed2d33d1d22300abb754f9085bc48bf8e8"
197
197
 
198
198
  [[package]]
199
199
  name = "diff_diff_rust"
200
- version = "3.1.0"
200
+ version = "3.1.1"
201
201
  dependencies = [
202
202
  "blas-src",
203
203
  "faer",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "diff_diff_rust"
3
- version = "3.1.0"
3
+ version = "3.1.1"
4
4
  edition = "2021"
5
5
  rust-version = "1.84"
6
6
  description = "Rust backend for diff-diff DiD library"
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
File without changes