Skip to content

Fix CausalForestDML ATE and ATT standard errors for multiple outputs - #1067

Open
kbattocchi wants to merge 1 commit into
mainfrom
kebatt/causal-forest-standard-errors
Open

kbattocchi wants to merge 1 commit into
mainfrom
kebatt/causal-forest-standard-errors

Conversation

@kbattocchi

@kbattocchi kbattocchi commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Summary

Fixes #1066.

Fix two errors in CausalForestDML's fit-time doubly robust training-data ATE/ATT inference:

  • Count non-missing predictions along the sample axis (axis=0), separately for each outcome-treatment pair and after ATT masking. Previously, pooling counts across outputs or treatment contrasts understated standard errors.
  • Preserve outcome-treatment ordering when constructing the doubly robust correction. The previous cross-product/reshape combination mixed the axes when both dimensions exceeded one, affecting point estimates as well as standard errors. Use an explicit einsum with sample, outcome, and treatment axes.

Regression coverage

  • Deterministic aggregation checks cover vector, single-column, and multi-output outcomes; binary and three-arm treatments; missing rows and unequal column missingness; and masked subsets.
  • Fitted-estimator checks cover ATE/ATT estimates, standard errors, confidence intervals, z-statistics, and p-values for every treatment group. They assert finite predictions and use ordinary means, standard deviations, and sample counts, with a comment explaining why the data and estimator seeds must be retained.
  • Duplicate-outcome invariance checks compare public ATE/ATT point estimates, standard errors, and confidence intervals before and after duplicating an outcome, for two, three, and four treatment arms. They do not recalculate a reference standard-error formula.

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

  • All 45 focused regression cases plus the existing causal-forest ATE inference test passed (46 total).
  • Independently restoring the original missing-axis bug in memory makes all three invariance cases fail.
  • Independently restoring the original correction ordering in memory makes the three- and four-arm invariance cases fail; the binary-treatment control still passes.

@kbattocchi
kbattocchi force-pushed the kebatt/causal-forest-standard-errors branch from a3c0ba5 to 983c0d7 Compare September 28, 2026 19:39
@kbattocchi
kbattocchi changed the base branch from main to kebatt/fix-ci-build-failures September 28, 2026 19:39
@kbattocchi
kbattocchi added this pull request to stack #1070 September 28, 2026 19:44
@kbattocchi
kbattocchi force-pushed the kebatt/causal-forest-standard-errors branch from 983c0d7 to d75bfa5 Compare September 28, 2026 19:51
Base automatically changed from kebatt/fix-ci-build-failures to main October 1, 2026 19:06
@kbattocchi
kbattocchi force-pushed the kebatt/causal-forest-standard-errors branch from d75bfa5 to 722add0 Compare October 1, 2026 19:06
@kbattocchi
kbattocchi force-pushed the kebatt/causal-forest-standard-errors branch 3 times, most recently from faec41e to a33ed7e Compare October 27, 2026 13:10
@kbattocchi
kbattocchi requested review from fverac and a balanced review from Copilot October 27, 2026 13:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 explicit einsum to 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)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Published the regression coverage in c76c65c, folded into the original fix. Production warning behavior is unchanged.

Comment thread econml/tests/test_dml.py Outdated
Comment on lines +97 to +104
# 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)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread econml/tests/test_dml.py Outdated
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))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@kbattocchi
kbattocchi force-pushed the kebatt/causal-forest-standard-errors branch from a33ed7e to c76c65c Compare October 27, 2026 19:24

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CausalForestDML(drate=True): ate_stderr_ / att_stderr_ are too small by sqrt(d_y*d_t) with multiple treatments or outcomes

2 participants