Is your feature request related to a problem? Please describe.
when using causalml in practice, i will often train S-learner, T-learner, X-learner, and DR-learner on the same data and have no principled way to pick between them. Standard cross validation does not work here because you never observe both potential outcomes for the same unit, so there's no ground truth to score against.
R-loss that powers the R-learner's training loop (rlearner.py lines 152, 495, 636) is exactly the right idea, but it's only used internally and never exposed as a standalone scoring function. EconML users already have this via econml.score.RScorer . Causalml users currently don't have an equivalent
Describe the solution you'd like
add a CATEModelSelector to causalml.metrics that scores and compares fitted meta-learners using observed-data surrogate metrics, without needing access to true counterfactual outcomes...
metrics worth shipping in a first pass,
- R-score (Nie and Wager, 2021): fits residual models for outcome and treatment, then measures how much CATE variance the model explains. Same R-loss already inside
BaseRLearner.fit() , just exposed as a standalone function
- DR pseudo-outcome loss (Kennedy 2023): constructs doubly-robust pseudo-outcomes and scores the CATE estimator against them. Mahajan et al. (2024) showed DR-based metrics dominate across 78 datasets
- Plug-in T-score: uses a T-learner on held-out data as a CATE proxy. Mahajan et al. found this is never dominated across datasets despite being a simple baseline
rough API sketch
from causalml.metrics import CATEModelSelector
selector = CATEModelSelector(
models={'s': s_learner, 't': t_learner, 'x': x_learner, 'dr': dr_learner},
metric='dr_loss', # or 'r_score', 'plug_in_t'
cv=5,
)
selector.fit(X, treatment, y)
selector.scores_ # {'s': 0.31, 't': 0.47, 'x': 0.61, 'dr': 0.58}
selector.best_model() # 'x'
selector.plot_scores()
no new heavy dependencies needed. Nuisance models reuse causalml's existing propensity module and sklearn
Describe alternatives you've considered
using EconML's RScorer directly works but requires fitting nuisance components twice across two libraries with no integration between them.
picking by held-out outcome MSE is what most people default to, but outcome MSE doesn't measure CATE accuracy. A model can have low outcome MSE while getting the treatment effect completely wrong.
Additional context
happy to start with just the R-score as a standalone function in causalml.metrics and build the selector wrapper from there, if that's a better way to scope an initial PR.
Is your feature request related to a problem? Please describe.
when using causalml in practice, i will often train S-learner, T-learner, X-learner, and DR-learner on the same data and have no principled way to pick between them. Standard cross validation does not work here because you never observe both potential outcomes for the same unit, so there's no ground truth to score against.
R-loss that powers the R-learner's training loop (rlearner.py lines 152, 495, 636) is exactly the right idea, but it's only used internally and never exposed as a standalone scoring function.
EconMLusers already have this viaeconml.score.RScorer. Causalml users currently don't have an equivalentDescribe the solution you'd like
add a
CATEModelSelectortocausalml.metricsthat scores and compares fitted meta-learners using observed-data surrogate metrics, without needing access to true counterfactual outcomes...metrics worth shipping in a first pass,
BaseRLearner.fit(), just exposed as a standalone functionrough API sketch
no new heavy dependencies needed. Nuisance models reuse causalml's existing propensity module and
sklearnDescribe alternatives you've considered
using EconML's RScorer directly works but requires fitting nuisance components twice across two libraries with no integration between them.
picking by held-out outcome MSE is what most people default to, but outcome MSE doesn't measure CATE accuracy. A model can have low outcome MSE while getting the treatment effect completely wrong.
Additional context
happy to start with just the R-score as a standalone function in
causalml.metricsand build the selector wrapper from there, if that's a better way to scope an initial PR.