Skip to content

Fix #1028: supply default LightGBM learner for cate scoring metrics - #1030

Merged
jeongyoonlee merged 1 commit into
uber:masterfrom
su-jin1425:fix-issue-1028
Aug 20, 2026
Merged

Fix #1028: supply default LightGBM learner for cate scoring metrics#1030
jeongyoonlee merged 1 commit into
uber:masterfrom
su-jin1425:fix-issue-1028

Conversation

@su-jin1425

Copy link
Copy Markdown
Contributor

Proposed changes

This PR fixes #1028 by providing a default LGBMRegressor when no outcome learner is supplied to the CATE scoring metrics.

Previously, _resolve_outcome_learners raised a ValueError when neither a learner nor both treatment/control outcome learners were provided. This prevented the metrics from being used with their expected default behavior.

The fix:

  • Uses LGBMRegressor as the default outcome learner.
  • Keeps the existing behavior when an explicit learner or both outcome learners are provided.
  • Preserves the existing ValueError fallback when LightGBM is unavailable.
  • Uses the existing LightGBM configuration: num_leaves=64, learning_rate=0.05, n_estimators=300.

Before

before

After

after

Test

Before

git checkout 35f6e734a478eea6fdf1cfaf1b85e0f04ae99b83; python -c "import numpy as np,pandas as pd; from causalml.metrics import dr_score,plug_in_t_score; rng=np.random.default_rng(42); X=rng.normal(size=(100,3)); w=rng.integers(0,2,100); y=1+X[:,0]+0.5*X[:,1]+2*w+rng.normal(size=100); df=pd.DataFrame({'y':y,'w':w,'model':2+0.5*X[:,0]}); expected='Either `learner` or both `control_outcome_learner` and `treatment_outcome_learner` must be specified.'; print('BEFORE: dr_score'); assert (lambda: True)(); exec('try:\n dr_score(df,X=X,outcome_col=\"y\",treatment_col=\"w\",n_folds=2,random_state=42)\nexcept ValueError as e:\n assert str(e)==expected\n print(\"PASS: expected ValueError\")'); print('BEFORE: plug_in_t_score'); exec('try:\n plug_in_t_score(df,X=X,outcome_col=\"y\",treatment_col=\"w\",n_folds=2,random_state=42)\nexcept ValueError as e:\n assert str(e)==expected\n print(\"PASS: expected ValueError\")')"

After

git checkout b889e916d0af9743e35b40fa907956dc3b25bc49; python -c "import numpy as np,pandas as pd; from causalml.metrics import dr_score,plug_in_t_score; rng=np.random.default_rng(42); X=rng.normal(size=(100,3)); w=rng.integers(0,2,100); y=1+X[:,0]+0.5*X[:,1]+2*w+rng.normal(size=100); df=pd.DataFrame({'y':y,'w':w,'model':2+0.5*X[:,0]}); print('AFTER: dr_score without learner'); r=dr_score(df,X=X,outcome_col='y',treatment_col='w',n_folds=2,random_state=42); print('PASS:',r); print('AFTER: plug_in_t_score without learner'); r=plug_in_t_score(df,X=X,outcome_col='y',treatment_col='w',n_folds=2,random_state=42); print('PASS:',r)"

Fixes #1028.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation Update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)
  • Any dependent changes have been merged and published in downstream modules

Further comments

The implementation is intentionally minimal and limited to the outcome-learner resolution path. Existing callers that provide their own learners are unaffected.

The relevant metric test completes successfully with the default learner. LightGBM emits warnings for some small test folds where no valid splits are available, but these are warnings from LightGBM and do not cause the test to fail.

Copilot AI balanced review requested due to automatic review settings August 18, 2026 04:04

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@jeongyoonlee jeongyoonlee left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please follow the existing convention in causalml/metrics/visualize.py rather than resolving the default inside the private helper.

  1. Move the default into the signatures of the three public entry points that take learner=Nonecompute_dr_pseudo_outcomes (:59), dr_score (:269), plug_in_t_score (:392) — as learner=LGBMRegressor(num_leaves=64, learning_rate=0.05, n_estimators=300), matching get_tmlegain / get_tmleqini / plot_tmlegain / plot_tmleqini (visualize.py:343, 453, 696, 740). The usual shared-instance concern with a mutable default doesn't apply here — _resolve_outcome_learners already deepcopys into each arm (:44, :49). Add verbose=-1 as in plot_tmlegain (:696) if you want to silence the "no further splits" warnings noted in the description.

  2. Drop the try/except ImportError. It can't fire: metrics/__init__.py imports .visualize (:13) before .cate_scoring (:30), visualize.py:7 imports lightgbm unconditionally, and lightgbm is a hard dependency (pyproject.toml:40). Import it at module top as visualize.py does.

  3. Update cate_scoring.py:315, which still reads "Required unless pseudo_outcome_col is provided". That sentence is what #1028 was about, and it is now wrong in the other direction. A signature default documents itself, which is most of the reason to prefer (1).

  4. Keep raising when exactly one of control_outcome_learner / treatment_outcome_learner is supplied. It currently pairs the given model with a default LightGBM for the other arm, and neither new test covers that path.

@su-jin1425

Copy link
Copy Markdown
Contributor Author

I’ve updated the implementation to follow the existing visualize.py convention by moving the default LGBMRegressor into the signatures of compute_dr_pseudo_outcomes, dr_score, and plug_in_t_score. I also removed the unnecessary ImportError handling, updated the outdated learner documentation, and added regression coverage for both the default learner path and the partial outcome-learner case.

@su-jin1425

Copy link
Copy Markdown
Contributor Author

Use #1030#1033#1032#1031 this order to avoid merge conflicts

Comment thread causalml/metrics/cate_scoring.py Outdated
"""
if (learner is None) and (
(control_outcome_learner is None) or (treatment_outcome_learner is None)
if (control_outcome_learner is None) != (treatment_outcome_learner is None) or (

@jeongyoonlee jeongyoonlee Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's split the condition into two, e.g., one with "!=", and the other with and/or for readability.

if (control_outcome_learner is None) != (treatment_outcome_learner is None):
    raise ValueError(
        "Specify both `control_outcome_learner` and `treatment_outcome_learner`, "
        "or neither."
    )
if learner is None and control_outcome_learner is None:
    raise ValueError(
        "Either `learner` or both `control_outcome_learner` and "
        "`treatment_outcome_learner` must be specified."
    )

@su-jin1425

Copy link
Copy Markdown
Contributor Author

Done

@su-jin1425

Copy link
Copy Markdown
Contributor Author

CI / CD failed so i worked on it.

Comment thread tests/test_cate_scoring.py Outdated
Comment on lines +553 to +554
import pytest
from sklearn.linear_model import LinearRegression

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

duplicate imports.

@su-jin1425

Copy link
Copy Markdown
Contributor Author

Done removing duplicate imports.

@jeongyoonlee
jeongyoonlee self-requested a review August 20, 2026 11:23

@jeongyoonlee jeongyoonlee left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM. Thanks!

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.

dr_score and plug_in_t_score document learner as optional but raise without it

3 participants