Repository navigation
Compute the ATE standard error from its influence function (#1006) - #1007
Merged
Merged
Conversation
CausalTreeRegressor.estimate_ate took se = dhat.std() / X.shape[0]. That divides by n rather than sqrt(n), and dhat.std() measures how the predicted effect varies across units rather than the sampling variability of its mean, so it carries no outcome noise. Over 100 randomized-trial draws at n=1000 the nominal 95% interval contained the true ATE 0 times. The standard error now comes from the estimate's influence function, adding the model's residuals on the observed outcomes to the spread of the predicted effects. With one treatment group this is the AIPW form, whose variance is the three-term expression BaseTLearner.estimate_ate already uses; the two agree to within a percent. Coverage on the same draws is 88%, the shortfall being that estimate_ate fits and estimates on the same rows (#517). A control unit enters every treatment group's contrast, so its residual is not divided by the number of them. That error is invisible with one treatment group, so a multi-arm test compares the reported error against the spread of the estimate over 40 draws, where it shows as 0.69 against 0.89. Closes #1006. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
6 of 10 tasks
jeongyoonlee
requested review from
huigangchen,
paullo0106,
ppstacy,
t-tte,
vincewu51 and
zhenyuz0500
August 15, 2026 14:36
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed changes
CausalTreeRegressor.estimate_atereported a confidence interval far too narrow to be useful. Closes #1006.The standard error was
dhat.std() / X.shape[0], which is wrong twice: the standard error of a mean divides bysqrt(n), notn; anddhat.std()measures how the predicted effect varies across units — real heterogeneity — rather than how far the sample mean of that effect would land from the truth on another draw. The second point matters more: no outcome noise enters it at all, so the interval stays too narrow after fixing the first.Point estimates do not change. Only the interval moves, and it gets wider.
The replacement
The standard error now comes from the estimate's influence function, which adds what the model got wrong on the observed outcomes to the spread of the predicted effects:
With one treatment group this is the AIPW influence function, and its variance is the three-term expression
BaseTLearner.estimate_atealready computes. The two agree to within a percent across seeds — the difference is cross terms that vanish when residuals are mean-zero — so this brings the causal tree onto the convention the meta-learners already follow rather than inventing one.A control unit enters every treatment group's contrast, so its residual is not divided by the number of them, while a treated unit's residual affects only its own.
Measured
20 randomized-trial draws,
synthetic_data(mode=2, n=2000)split in half, fitted on one half and estimated on the other (n=1000), nominal 95%. Coverage counts the seeds whose interval contains the true ATE, so 19 of 20 is the target:std(dhat) / n(before)std(dhat) / sqrt(n)Over 100 draws the shipped path covers 88%. The gap to 95% is that
estimate_atefits and estimates on the same rows, which makes the residuals optimistic — that is #517, and this PR does not change it.Tests
Four, in
tests/test_causal_trees.py:rel=0.05);sqrt(4) = 2, not 4, when the sample quadruples — the scaling error is invisible at a single sample size;The last one exists because of a mutation that survived everything else: dividing the control residual by
n_treatmentsis plausible — each group's contrast is averaged, after all — and is a no-op when there is one treatment group. It takes that ratio from 0.89 to 0.69.Types of changes
Checklist
Further comments
The interval has been this way since v0.8.0 (2020-07-17); #522 moved the expression into the current file during the scikit-learn rewrite, so
git blamepoints there rather than at the origin.CausalRandomForestRegressorhas noestimate_ateof its own and is unaffected.causaltree.py:431was the only occurrence of the pattern in the package.