Repository navigation
Conversation
LightGBMLocationSelector always trained LightGBM's binary objective on the raw
labels. Labels other than {0, 1} looked single-class, so no split was found and
placement silently fell back to quantiles; multiclass targets only found the
class-0 boundary; and string labels raised in fit.
For task="classification" the target is now label-encoded and trained with the
binary objective for two classes and the multiclass objective (num_class) for
more, matching what the CART selector supports. A single-class target has no
split to learn and takes the unsupervised fallback instead of failing.
Fixes #61
This was referenced Oct 16, 2026
This branch has not been deployed
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.
Fixes #61.
With
placement_strategy="lightgbm"andtask="classification", target-aware placement handled only targets coded with one class ≤ 0 and the other > 0. For labels such as{1, 2}it silently fell back to quantiles, for multiclass targets it found only the class-0 boundary, and for string labelsfitraised. This affected PLE, the feature maps and the knot splines.Root cause
LightGBMLocationSelector._ordered_candidatessetobjective="binary"for every classification task and passed the raw labels tolgb.Dataset. LightGBM's binary objective needs numeric labels and treats every label > 0 as positive. There was no label encoding and no multiclass objective. When no split was found,selectfell back to quantile locations without a warning.Fix
In
LightGBMLocationSelector._ordered_candidates, a classification target is label-encoded withnp.unique(y_valid, return_inverse=True)and the objective follows the number of classes:binary(binary_logloss);multiclasswithnum_class(multi_logloss);selecttakes its unsupervised fallback.Regression is unchanged.
Behaviour changes
task="classification"now follows the class boundaries for labels other than{0, 1}, for multiclass targets and for string labels. In the issue's example, the{1, 2}target gets[0.901 0.972]instead of the quantiles[0.367 0.685], the three-class target gets[0.094 0.901], and string labels no longer raise.{0, 1}targets are unaffected: their codes equal the labels.Documentation
LightGBMLocationSelectorclass docstring: new paragraph on the classification handling (label encoding, so any class labels work as with the CART selector;binaryfor two classes,multiclassfor more; a single class falls back to unsupervised locations).docs/core_concepts/resolution_and_placement.mdpresents"lightgbm"as an alternative to"cart"with no restriction on the labels, which now holds.Tests
test_lightgbm_classification_places_a_location_at_the_class_boundary[labels-1-2, three-classes, string-labels]: a location lies within 0.01 of the class boundary at 0.9. Fails onmain, passes here.test_lightgbm_classification_with_a_single_class_falls_back: a single-class string target returns the unsupervised fallback[1/3, 2/3]onlinspace(0, 1, 200). Fails onmain(ValueError: could not convert string to float), passes here.Reproduction
Output of the script from #61 on this branch (
LeakageWarnings omitted):Checks
just test: 1442 passed, 59 skipped, 7 xfailed; coverage 93.67% (gate 90%).scripts/quickstart.pypasses. The new tests skip there becauselightgbmis not installed.just docs: strict build succeeds.just check: all pre-commit and pre-push hooks pass (ruff format, ruff lint, prettier, file hygiene, pyright with 0 errors).Merge notes
main.pretab/core/selectors.py, but in other functions, and the code merges cleanly. All three append tests at the end oftests/core/test_location_selectors.py, so whichever lands later gets an append/append conflict there: keep both blocks. With Target-aware location selectors return too few, duplicate, or boundary locations on tied/discrete features #57, the single-class fallback goes through its new_supplement, which returns the same[1/3, 2/3]for this PR's test. With all three branches combined, the full test suite passes (1478 passed, 59 skipped, 7 xfailed).paramsdict ofLightGBMLocationSelector._ordered_candidates. An invalid task such as "Regression" is silently treated as classification by the splines and selectors #93 rewrites theobjective/metriclines, and this PR replaces those same lines with**objective. Keep this PR's**objective. Once An invalid task such as "Regression" is silently treated as classification by the splines and selectors #93 validatestaskinselect, theelsebranch here only ever sees"classification".RepresentationSearchCVaround a classifier passestask="classification"to its preprocessors. String labels withpreprocessor_params={"placement_strategy": "lightgbm"}work only once this PR is merged as well.This change was previously part of #75, which is closed in favour of one pull request per issue.