Skip to content

fix(validation): reject an unknown task instead of using classification - #128

Open
ChrisW09 wants to merge 1 commit into
mainfrom
fix/93-validate-task
Open

ChrisW09 wants to merge 1 commit into
mainfrom
fix/93-validate-task

Conversation

@ChrisW09

@ChrisW09 ChrisW09 commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #93.

An invalid task such as the typo "Regression" was accepted by the knot splines, the Preprocessor configuration and the location selectors, and silently treated as classification. Users got classification-tree knots, LightGBM's binary objective on a regression target, an unrelated scikit-learn "Unknown label type: continuous" error on a float target, or the classification preset method "ple".

Root cause

task was validated only by PLETransformer, the feature maps and CrossFittedTransformer, each with its own message. BaseLocationSelector.select took task or "regression" unchecked, and the CART and LightGBM selectors dispatched with if task == "regression": ... else: <classification>, so every other value took the classification branch. PreprocessorConfig.from_params did not check task, and _preset_numerical_method returned "ple" for anything but "regression".

Fix

  • New validate_task(task, estimator, *, allow_none=False) and TASKS in pretab/core/parameters.py. The match is exact, and an invalid value raises InvalidParamError through invalid_param_error, so every estimator reports it in the same format.
  • validate_task runs at fit in BaseSplineTransformer (B-, M- and I-splines), NaturalCubicSplineTransformer and CubicRegressionSplineTransformer (with allow_none=True; None still means regression). PLETransformer, BaseCenterExpansion (the feature maps) and CrossFittedTransformer._fit_full use it instead of their own checks.
  • BaseLocationSelector.select validates task (None is treated as "regression"). CARTLocationSelector and LightGBMLocationSelector now choose the classifier tree and the binary objective only for task == "classification".
  • Preprocessor._resolved_params validates task before a preset derives numerical_method from it, so fit and get_resolved_config() both raise. PreprocessorConfig.from_params validates it as well.

Behaviour changes

  • Any task other than "regression" or "classification" raises InvalidParamError: at fit of every transformer that takes task, in the location selectors, and in Preprocessor.fit / get_resolved_config(). The Preprocessor raises even when the chosen method ignores the task (for example minmax).
  • Preprocessor(task=None) now raises. Before, the target-aware splines treated it as regression and untargeted methods ignored it, while ple and every preset rejected it with PLE's error. The standalone splines and the location selectors still accept None as "regression".
  • The invalid-task messages of PLETransformer, the feature maps (rbf, relu, sigmoid, tanh) and CrossFittedTransformer change to the shared format, for example PLETransformer.task = 'Regression' is invalid. followed by the constraint and the valid values. The exception type stays InvalidParamError.
  • New public helper pretab.core.parameters.validate_task and constant TASKS. Valid task values give the same results as before.

Documentation

  • docs/core_concepts/configuration.md: the presets paragraph says that task is matched exactly and that any other value, such as "Regression", raises an InvalidParamError from fit and get_resolved_config() instead of falling back to the classification preset. The "validated at fit time" warning lists an invalid task.
  • Docstrings: validate_task; BaseLocationSelector.select (task, new Raises); the pretab/compose/config.py module docstring, PreprocessorConfig and its from_params Raises; the Preprocessor task parameter, and Raises sections for _resolved_params and get_resolved_config; _preset_numerical_method; the task parameter of BaseSplineTransformer, CubicRegressionSplineTransformer and NaturalCubicSplineTransformer.

Tests

  • test_invalid_task_raises[Regression, classif, None] (tests/compose/test_config.py): PreprocessorConfig rejects each value. Fails on main, passes here.
  • test_selectors_reject_an_unknown_task (CART and LightGBM, each with "Regression", "regresion", "" and 1): select raises InvalidParamError. Fails on main, passes here.
  • test_unknown_task_raises_at_fit[bspline, cubicspline, ispline, mspline, naturalspline, ple, rbf, relu, sigmoid, tanh]: every registered method with a task parameter raises the shared message at fit for task="Regression". Fails on main, passes here; the knot splines fitted there, and ple and the feature maps raised with their old messages.
  • test_preprocessor_unknown_task[bspline, naturalspline, rbf, ple, minmax]: Preprocessor(task="Regression").fit raises Preprocessor.task = 'Regression' is invalid. Fails on main, passes here.
  • test_invalid_task_raises_instead_of_resolving[None, standard, expanded, adaptive] (tests/integration/test_presets.py): get_resolved_config() raises instead of resolving a method. Fails on main, passes here.
  • test_ple_invalid_task_raises, test_ple_unsupported_task, test_rbf_invalid_task, test_relu_invalid_task, test_sigmoid_invalid_task and test_tanh_invalid_task_raises: updated to the shared message. They fail on main only because of the wording.
  • test_cart_task_none_means_regression and test_spline_task_none_places_knots_for_regression: task=None still gives the regression locations and knots. Guard tests: pass on both by design.

Reproduction

Output of the script from #93 on this branch, with each call wrapped separately. The last line is an added check that a valid task still resolves the preset as before:

PLETransformer(task='Regression') -> InvalidParamError: PLETransformer.task = 'Regression' is invalid.
Constraint: must be 'regression' or 'classification'
Valid values: ['classification', 'regression']
RBFExpansionTransformer(task='Regression') -> InvalidParamError: RBFExpansionTransformer.task = 'Regression' is invalid.
Constraint: must be 'regression' or 'classification'
Valid values: ['classification', 'regression']
BSplineTransformer(task='Regression') -> InvalidParamError: BSplineTransformer.task = 'Regression' is invalid.
Constraint: must be 'regression', 'classification', or None (treated as 'regression')
Valid values: ['classification', 'regression']
Preprocessor('bspline', task='Regression') -> InvalidParamError: Preprocessor.task = 'Regression' is invalid.
Constraint: must be 'regression' or 'classification'
Valid values: ['classification', 'regression']
Preprocessor('naturalspline', task='Regression') -> InvalidParamError: Preprocessor.task = 'Regression' is invalid.
Constraint: must be 'regression' or 'classification'
Valid values: ['classification', 'regression']
Preprocessor('bspline', task='Regression'), float y -> InvalidParamError: Preprocessor.task = 'Regression' is invalid.
Preprocessor(preset='standard', task='Regression').get_resolved_config() -> InvalidParamError: Preprocessor.task = 'Regression' is invalid.
Constraint: must be 'regression' or 'classification'
Valid values: ['classification', 'regression']
Preprocessor(preset='standard', task='regression') resolves numerical_method to 'bspline'

Checks

  • just test: 1470 passed, 59 skipped, 7 xfailed; coverage 93.66% (gate 90%).
  • Minimum dependencies (CI job: Python 3.10, numpy 1.24.4, pandas 2.0.3, scipy 1.10.1, scikit-learn 1.6.0): 1457 passed, 72 skipped, 7 xfailed; scripts/quickstart.py passes.
  • 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).
  • Import direction between subpackages is unchanged.

Merge notes

This change was previously part of #97, which is closed in favour of one pull request per issue.

The knot splines, the CART / LightGBM location selectors and the Preprocessor
config never checked task, and the selectors treated every value other than
"regression" as classification. A typo such as task="Regression" therefore gave
classification-tree knots, failed deep inside scikit-learn on a float target
("Unknown label type: continuous"), fit a continuous target with LightGBM's
binary objective, and made every preset resolve numerical_method to "ple".

A shared validate_task helper in pretab.core.parameters now checks task at fit
in the B/M/I, natural cubic and cubic regression splines (None still means
"regression"), PLE, the feature maps, CrossFittedTransformer, the location
selectors and PreprocessorConfig.from_params. Preprocessor validates it before
preset resolution, so fit and get_resolved_config raise for any method or
preset; the selectors dispatch explicitly on "classification".

All of them raise InvalidParamError with one message
("<Estimator>.task = 'Regression' is invalid. ..."), replacing the differing
PLE, feature-map and CrossFittedTransformer wording.

Fixes #93

This branch has not been deployed

No deployments
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.

An invalid task such as "Regression" is silently treated as classification by the splines and selectors

1 participant