Skip to content

Automatically set type to raw to avoid emitting warnings - #29

Merged
wagnerlmichael merged 6 commits into
masterfrom
mwagner/route-custom-obj-func-warning
Sep 28, 2026
Merged

wagnerlmichael merged 6 commits into
masterfrom
mwagner/route-custom-obj-func-warning

Conversation

@wagnerlmichael

@wagnerlmichael wagnerlmichael commented Sep 8, 2026 •

Copy link
Copy Markdown
Member

The context

Issue - #28

When we supply a custom objective function mse_cov, we set params$objective to "none"

if (is.function(params$objective)) {
    fobj <- params$objective
    params$objective <- "none"
}

which sets off this guard in predict.lgb.Booster

if (!is.null(object$params$objective) && object$params$objective == "none"
    && type %in% c("class", "response")) {
    warning("Prediction types 'class' and 'response' are not supported for custom objectives.")
    type <- "raw"
}

This doesn't affect the actual model fitting, it just throws out a bunch of warnings when we train models.

The fix

In pred_lgb_reg_num, the function used for prediction, we make a conditional change. It says "if we had set params$objective to "none" (our proxy for custom obj function), then we set type to "raw".

This pred_lgb_reg_num calls stats::predict(object$fit, ....). Which is then routed to lightgbm:::predict.lgb.Booster which has the type argument that will now get set to "raw", rather than default to its default "response" value.

You can verify the change in CV logging by comparing how the warning aggregation looked before we made this change:

select * from model.parameter_search
where run_id = '2026-08-26-fervent-matt'

to how it looks now

select * from model.parameter_search
where run_id = '2026-09-09-elated-matthew'

How is stats::predict(object$fit, ....) routed to lightgbm:::predict.lgb.Booster?

Reviewers of this PR may already know this, but this was very mysterious for me. stats::predict is a generic R function, which means that the actual predict function that gets used is dependent upon the class of the first argument, in this case - object$fit. object$fit is class lgb.Booster and as such it recruits predict.lgb.Booster:

predict.lgb.Booster <- function(object,
                                newdata,
                                type = "response",
                                start_iteration = NULL,
                                num_iteration = NULL,
                                header = FALSE,
                                params = list(),
                                ...) {

which has the type parameter and gets passed our initially defined "raw" from pred_lgb_reg_num

@wagnerlmichael wagnerlmichael changed the title Add first draft Automatically set type to raw to avoid emitting warnings Sep 8, 2026
@codecov

codecov Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.79%. Comparing base (b8ed4fc) to head (d17413b).
⚠️ Report is 6 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master      #29      +/-   ##
==========================================
+ Coverage   81.20%   85.79%   +4.59%     
==========================================
  Files           3        4       +1     
  Lines         133      176      +43     
==========================================
+ Hits          108      151      +43     
  Misses         25       25              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread R/lightgbm.R
#
# Note that stats::predict() is a generic: because object$fit is an
# lgb.Booster, the call is routed to predict.lgb.Booster(), which is
# where `type` actually lands.

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'm not sure if this level of verbosity is needed, but I dropped it here because this generic comment would have saved me hours of time

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[Praise] I like it! This behavior is definitely confusing, so it's well worth the comment in my opinion.

@wagnerlmichael
wagnerlmichael marked this pull request as ready for review September 10, 2026 18:55

@jeancochrane jeancochrane left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice job getting to the bottom of a tricky warning! Your explanations make sense and I think this is a good way of fixing the issue. Note that we'll need to merge corresponding res and condo model PRs to bump the version of this package before we can close #28.

Comment thread R/lightgbm.R
#
# Note that stats::predict() is a generic: because object$fit is an
# lgb.Booster, the call is routed to predict.lgb.Booster(), which is
# where `type` actually lands.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[Praise] I like it! This behavior is definitely confusing, so it's well worth the comment in my opinion.

Comment on lines +97 to +100
# lightgbm stores custom objectives as "none", so predict.lgb.Booster()
# has no way to produce the default "response" prediction type: it warns
# and falls back to "raw". pred_lgb_reg_num() detects this case and asks
# for "raw" directly, which avoids that warning.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[Question, non-blocking] I wonder if there's a way to test that the type argument to stats::predict() is set properly depending on the objective and/or the type keyword argument in pred_lgb_reg_num()? If so, it probably merits its own unit test to test the different configurations (i.e. type argument present; type argument not present with no objective; type argument not present with an objective). I assume this would require mocking out stats::predict(), which might be tricky, so I'm also fine not testing it if it's too troublesome.

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.

Let me know what you think of this - 8b3b045#r4095655023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[Praise] Looks great!

Comment thread DESCRIPTION
scales,
snakecase,
testthat (>= 3.0.0),
testthat (>= 3.2.0),

@wagnerlmichael wagnerlmichael Sep 24, 2026 •

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.

@jeancochrane jeancochrane left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Awesome job! I'm glad to see this fixed.

Comment on lines +97 to +100
# lightgbm stores custom objectives as "none", so predict.lgb.Booster()
# has no way to produce the default "response" prediction type: it warns
# and falls back to "raw". pred_lgb_reg_num() detects this case and asks
# for "raw" directly, which avoids that warning.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[Praise] Looks great!

@wagnerlmichael
wagnerlmichael merged commit 37d35a2 into master Sep 28, 2026
11 checks passed
@wagnerlmichael
wagnerlmichael deleted the mwagner/route-custom-obj-func-warning branch September 28, 2026 13:44
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.

Fix warning about prediciton types that are not supported for custom objectives

2 participants