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.
- {diff_diff-3.1.0 → diff_diff-3.1.1}/PKG-INFO +1 -1
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/__init__.py +1 -1
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/results.py +7 -2
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/synthetic_did.py +233 -9
- {diff_diff-3.1.0 → diff_diff-3.1.1}/pyproject.toml +1 -1
- {diff_diff-3.1.0 → diff_diff-3.1.1}/rust/Cargo.lock +1 -1
- {diff_diff-3.1.0 → diff_diff-3.1.1}/rust/Cargo.toml +1 -1
- {diff_diff-3.1.0 → diff_diff-3.1.1}/README.md +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/_backend.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/bacon.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/bootstrap_utils.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/chaisemartin_dhaultfoeuille.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/chaisemartin_dhaultfoeuille_bootstrap.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/chaisemartin_dhaultfoeuille_results.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/continuous_did.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/continuous_did_bspline.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/continuous_did_results.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/datasets.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/diagnostics.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/efficient_did.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/efficient_did_bootstrap.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/efficient_did_covariates.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/efficient_did_results.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/efficient_did_weights.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/estimators.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/honest_did.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/imputation.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/imputation_bootstrap.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/imputation_results.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/linalg.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/power.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/practitioner.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/prep.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/prep_dgp.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/pretrends.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/stacked_did.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/stacked_did_results.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/staggered.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/staggered_aggregation.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/staggered_bootstrap.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/staggered_results.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/staggered_triple_diff.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/staggered_triple_diff_results.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/sun_abraham.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/survey.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/triple_diff.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/trop.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/trop_global.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/trop_local.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/trop_results.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/twfe.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/two_stage.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/two_stage_bootstrap.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/two_stage_results.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/utils.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/visualization/__init__.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/visualization/_common.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/visualization/_continuous.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/visualization/_diagnostic.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/visualization/_event_study.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/visualization/_power.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/visualization/_staggered.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/visualization/_synthetic.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/wooldridge.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/diff_diff/wooldridge_results.py +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/rust/build.rs +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/rust/src/bootstrap.rs +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/rust/src/lib.rs +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/rust/src/linalg.rs +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/rust/src/trop.rs +0 -0
- {diff_diff-3.1.0 → diff_diff-3.1.1}/rust/src/weights.rs +0 -0
|
@@ -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'.
|
|
222
|
-
strata/PSU/FPC; use
|
|
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
|
|
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
|
|
286
|
+
and self.variance_method != "bootstrap"
|
|
281
287
|
):
|
|
282
288
|
raise NotImplementedError(
|
|
283
|
-
"SyntheticDiD with variance_method='
|
|
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
|
-
|
|
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.
|
|
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"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|