Add support for user-supplied propensities in DML and DRLearner estimators#1040
Open
Oren-T wants to merge 1 commit into
Open
Add support for user-supplied propensities in DML and DRLearner estimators#1040Oren-T wants to merge 1 commit into
Oren-T wants to merge 1 commit into
Conversation
…ators Signed-off-by: Oren Tirschwell <orendev4@gmail.com>
36e7a28 to
a8c9d2d
Compare
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.
Motivation
If you have data from a randomized experiment with a known assignment design — say subjects randomized within blocks, where the true propensity is just the known treatment probability within each block — there's currently no way to tell the DML or DR learners about it. The estimators always fit
model_t/model_propensityinternally, and the cross-fitting machinery makes it effectively impossible to inject precomputed values from outside: models are cloned per fold and only ever see anonymous row slices, so even a custom "model" holding the known values can't align them to the rows it's asked to score.This has come up repeatedly: #730 asks for exactly this for
ForestDRLearner, #535 for A/B test data, and #646 for reusable first-stage models more generally. The workarounds suggested in those threads (aDummyClassifierfor constant probabilities, or appending the propensity as the last column of W with a passthrough model) do work in narrow cases, but they're fragile and roundabout — and notably, the closure-over-an-array variant some users reach for is silently wrong under cross-fitting and bootstrap resampling, since the wrapper has no way to know which rows it's being asked about. In the block-randomized case, fitting a model to estimate something you know by design is strictly worse than just using the known values.What this adds
A
propensitykeyword onfit(andscore) of the DML family (whendiscrete_treatment=True) and the DRLearner family:When provided, the first-stage treatment/propensity model is not fitted at all; the supplied values are used directly in residualization (DML:
T_res = T_onehot - p[:, 1:]) or in the doubly robust correction (DRLearner, where they remain subject tomin_propensityclipping andtrimming_thresholdtrimming). Binary treatments accept a single column (the probability of the non-control treatment); multi-valued treatments take one column per category, ordered to match the fitted categories.One point the docs are careful about: the supplied values must be the true by-design probabilities conditional on everything that influenced assignment — including design variables like randomization blocks even when those aren't part of X or W. That conditioning set is what identification actually requires (and it's what makes this feature useful in the first place: in a block design where the blocks aren't recoverable from X/W, no fitted propensity model can be correct, but the known per-unit rate is).
The implementation follows the existing pattern for per-row fit arguments (
sample_var,freq_weight,Z): the array is validated in_OrthoLearner.fitand plumbed through_fit_nuisancesinto_crossfit, whose kwargs mechanism already slices per fold. As a result cross-fitting,mc_iters,cache_values/refit_final,groups, and bootstrap inference all compose with it without special handling. Estimators that don't model propensities are unaffected — the kwarg defaults toNoneeverywhere and is rejected where unsupported. Inscoresignatures the new parameter is placed last, so existing positional callers are unaffected (there's a test pinning this).A few behavioral notes:
scoreon new data also requires thepropensityargument (a clear error message explains this).models_t/models_propensityraise an informative error after a bypassed fit instead of returning never-fitted models;nuisance_scores_t/nuisance_scores_propensityentries areNone.Testing
New test module
econml/tests/test_user_propensity.py(22 tests), including: a hidden-block RCT where the supplied propensities recover the ATE with covering confidence intervals while a fitted propensity model is badly biased; exact treatment-residual identities; proof the first-stage model is never fitted (via a classifier whosefitraises); multi-valued treatments with unit-varying propensities; category-ordering checks with a swapped-column negative control; clipping/trimming interaction vian_samples_trimmed_; composition withgroups,freq_weight/sample_var,mc_iters,refit_final, and bootstrap inference; positional-argument compatibility ofscore; and input validation. The existingtest_dml,test_drlearner,test_ortho_learner,test_refit, andtest_inferencesuites all pass. The doc FAQ snippets added todml.rst/dr.rstwere executed against the testsetup data.Natural follow-ups not included here to keep the change contained: accepting
propensityinCausalForestDML.tune(),RScorer, and the policy learners.Fixes #730. Also addresses the propensity side of #535 and #646.