Repository navigation
Fix CausalForestDML ATE and ATT standard errors for multiple outputs - #1067
kbattocchi wants to merge 1 commit into
Conversation
a3c0ba5 to
983c0d7
Compare
983c0d7 to
d75bfa5
Compare
d75bfa5 to
722add0
Compare
faec41e to
a33ed7e
Compare
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
3 open issues
What changed in this PR
Fixes fit-time doubly robust ATE/ATT inference in CausalForestDML for multi-output outcomes / multi-arm treatments by correcting both the per-output sample counting for standard errors and the axis ordering in the DR correction term.
Changes:
- Count non-missing DR predictions per outcome-treatment column (and after ATT masking) when computing standard errors.
- Replace
cross_product(...).reshape(...)with an expliciteinsumto preserve (sample, outcome, treatment) axis ordering in the DR correction. - Add regression tests covering multi-output, multi-arm, missingness patterns, ATT masking, and duplicate-outcome invariance.
| File | Description |
|---|---|
| econml/dml/causal_forest.py | Fixes DR correction axis mixing and computes per-column non-missing counts for ATE/ATT stderr. |
| econml/tests/test_dml.py | Adds targeted regressions validating ATE/ATT point estimates, stderr, and inference outputs across shapes/arms/missingness. |
Review effort: Lite
| point = np.nanmean(drpreds, axis=0).reshape(self._d_y + self._d_t) | ||
| nonnan = np.sum(~np.isnan(drpreds)) | ||
| nonnan = np.sum(~np.isnan(drpreds), axis=0) | ||
| stderr = (np.nanstd(drpreds, axis=0) / np.sqrt(nonnan)).reshape(self._d_y + self._d_t) |
There was a problem hiding this comment.
I checked both versions on an all-NaN column: both return NaN and emit the same nanmean/nanstd warnings; the new denominator does not introduce an additional division warning. Replacing zero denominators would not suppress those reduction warnings. I'd retain the diagnostic rather than silently hide an unavailable estimate. I've added regression cases for all-missing columns, with and without a sample mask, checking NaN results and unaffected valid columns. That test-only follow-up is committed locally, pending publication approval.
There was a problem hiding this comment.
Published the regression coverage in c76c65c, folded into the original fix. Production warning behavior is unchanged.
| # Keep the data and estimator seeds: this reference calculation relies on their | ||
| # producing finite predictions, including an OOB prediction for every observation. | ||
| assert np.isfinite(drpreds).all() | ||
| expected_shape = outcome_shape + (n_arms - 1,) | ||
| for arm in [None, *range(n_arms)]: | ||
| selected = drpreds if arm is None else drpreds[T == arm] | ||
| expected_point = selected.mean(axis=0).reshape(expected_shape) | ||
| expected_stderr = (selected.std(axis=0) / np.sqrt(selected.shape[0])).reshape(expected_shape) |
There was a problem hiding this comment.
The finite-OOB assertion is intentional for this fixed-seed test: it lets the reference use ordinary mean/std and the observation count instead of reproducing production's NaN-handling logic. Missing rows and unequal per-column coverage are exercised separately in test_cfdml_dr_stderr_per_column. If these seeds stop giving full OOB coverage, we'd rather update this fixture explicitly than silently change what the test checks.
| values = selected[:, y, t] | ||
| values = values[~np.isnan(values)] | ||
| expected_point[y, t] = values.mean() | ||
| expected_stderr[y, t] = values.std() / np.sqrt(len(values)) |
There was a problem hiding this comment.
Agreed—making the convention explicit improves readability. I've added ddof=0 to both the per-column and fitted-estimator reference calculations. This preserves NumPy's existing default and does not change production behavior. The test-only follow-up is committed locally, pending publication approval.
There was a problem hiding this comment.
Published the explicit ddof=0 references in c76c65c, folded into the original fix.
Count non-missing predictions along the sample axis and preserve outcome-treatment ordering when forming doubly robust corrections. Cover masked and missing predictions, fitted-estimator inference, and duplicate-outcome invariance with two, three, and four treatment arms. Keep API docstrings focused on caller-facing behavior and make the finite-data reference calculation's seed dependence explicit. Fixes #1066 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Keith Battocchi <kebatt@microsoft.com>
a33ed7e to
c76c65c
Compare


Summary
Fixes #1066.
Fix two errors in CausalForestDML's fit-time doubly robust training-data ATE/ATT inference:
axis=0), separately for each outcome-treatment pair and after ATT masking. Previously, pooling counts across outputs or treatment contrasts understated standard errors.einsumwith sample, outcome, and treatment axes.Regression coverage
The invariance test exposed the correction-order error. The existing known-ground-truth test uses duplicate outcomes, identical true effects for both non-control treatments, and absolute tolerances that allowed this error to escape; recomputing inference from the estimator's internal corrected predictions also cannot detect incorrect corrections upstream.
Validation