Repository navigation
Conversation
…rget The B-, M- and I-spline transformers flattened y and masked it with each feature's missing-value mask even when no target-aware selector used it, so a multi-output target of n rows (2n values after ravel) raised an IndexError in fit. make_pipeline(BSplineTransformer(), Ridge()).fit(X, Y) with a 2-D Y failed, as did explicit knot_locations and the Preprocessor with numerical_method="bspline" / "mspline" / "ispline" and target_aware=False, while scikit-learn's SplineTransformer and the other spline families and feature maps ignore y there. Target-aware placement fits one decision tree or boosting model on one target, but no error said so: the B/M/I splines (including the regression presets) hit the same IndexError before the selector ran, the shared location selector flattened y and failed with a NumPy broadcasting error (natural-cubic, cubic-regression, feature maps), and PLE, the Preprocessor's default method, reported "X and y must have same length". y is now read only when the target-aware selector places the knots; the unsupervised and explicit knot_locations paths ignore it. On the selector path y is sliced by rows like X instead of being flattened. A shared single_target() check in the location selector and PLE flattens a column vector as before and raises an IncompatibleParamsError for more than one output column, pointing to target_aware=False. Fixes #88
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 #88.
A 2-D (multi-output)
ymadeBSplineTransformer,MSplineTransformerandISplineTransformerraise anIndexErrorinfit, even with unsupervised or explicit knot placement whereyis never used, so a standard multi-output pipeline such asmake_pipeline(BSplineTransformer(), Ridge()).fit(X, Y)failed. Target-aware placement, which needs a single target, failed with unrelated errors instead of saying so.Root cause
BaseSplineTransformer.fitbuilty_arr = np.asarray(y).ravel()and sliced it with each feature's NaN mask on every path, not only when a selector used it. A target of shape(n, k)becamen * kvalues, which a mask of lengthncannot index.BaseLocationSelector.selectandPLETransformer.fitflattenedythe same way, so on the target-aware paths of the other families a multi-output target failed with a NumPy broadcastingValueError(combining the NaN masks ofxandy) or with PLE's "X and y must have same length" check.Fix
BaseSplineTransformer.fitbuildsy_arronly when the target-aware selector (SplinePlacementAdapter) places the knots, and slices it by rows likeXinstead of flattening it. The unsupervised and explicit-knot_locationspaths ignorey.pretab.core.validation.single_target(y, estimator, *, fix=None): returns a 1-D target or a column vector flattened, as before, and raisesIncompatibleParamsErrornaming the estimator and the shape ofywhen it has more than one output column. The message points to one target column ortarget_aware=False.target_aware=False, whilePLETransformer, which is always target-aware and has no such option, suggests fitting on one target column or choosing a method with unsupervised placement.BaseLocationSelector.select(the CART and LightGBM selectors behind target-aware B/M/I, natural-cubic and cubic-regression splines and the RBF/ReLU/sigmoid/tanh feature maps) andPLETransformer.fitcallsingle_targetinstead ofnp.asarray(y).ravel().Behaviour changes
target_aware=Falseor explicitknot_locationsignoreyentirely, whatever its shape and even when it is not row-aligned withX; knots and output equal those offit(X). Multi-output pipelines and the unsupervisedPreprocessormethods"bspline","mspline"and"ispline"now fit.y(B/M/I, natural-cubic, cubic-regression, RBF/ReLU/sigmoid/tanh feature maps and PLE, with"cart"or"lightgbm"; this includes thePreprocessordefaultpleand the regression presets) raisesIncompatibleParamsError: <CARTLocationSelector | LightGBMLocationSelector | PLETransformer> places locations against a single target, but y has shape (200, 2).followed by aFix:line. Before, it raised anIndexError, a broadcastingValueErroror, for PLE, aPretabDataError.pretab.core.validationexports the new helpersingle_target.Documentation
target_awareparameter ofBaseSplineTransformer(Trueneeds a single-targety, a multi-outputyraises;Falseand explicitknot_locationsignorey), theyparameter ofBaseLocationSelector.selectandPLETransformer.fit(shape(n_samples,)or(n_samples, 1), a multi-outputyraises), andsingle_targetwith aRaisessection.docs/core_concepts/target_awareness.md: new paragraph. Target-aware placement fits one model on a single target, so a multi-outputyraisesIncompatibleParamsErrorforpleand for anoptionalmethod withtarget_aware=True; a column vector counts as a single target; withtarget_aware=Falsetheoptionalmethods ignoreyand fit inside a multi-output pipeline.Tests
test_unsupervised_bmi_spline_ignores_a_multi_output_target[{B,M,I}SplineTransformer x default | uniform | target-aware with explicit knots]: fitting with a 2-DY(and a NaN inX) gives the same knots and output as fitting withouty. Fails onmain, passes here.test_unsupervised_bmi_spline_never_reads_y[{B,M,I}SplineTransformer x default | uniform]: ayof the wrong length is ignored. Fails onmain, passes here.test_bmi_spline_fits_in_a_multi_output_pipeline[{B,M,I}SplineTransformer]:make_pipeline(cls(), Ridge())with a 2-DYpredicts shape(200, 2). Fails onmain, passes here.test_target_aware_bmi_spline_accepts_a_column_vector_target[{B,M,I}SplineTransformer]: a column-vectorygives the same knots as the flatyon the new row-sliced selector path. Guard test: passes on both by design.test_target_aware_placement_rejects_a_multi_output_target[{BSpline, MSpline, ISpline, NaturalCubic, CubicReg, RBF, ReLU, Sigmoid, Tanh, PLE} x {cart, lightgbm}](edge-case contract): a 2-DYraisesIncompatibleParamsErrornaming the shape, and a column vector gives the same output as the flat target. All 20 fail onmain, pass here; thelightgbmcases skip without LightGBM.test_unsupervised_numerical_method_accepts_a_multi_output_target[bspline, mspline, ispline, pspline, naturalspline, rbf](Preprocessor,target_aware=False): the output with a 2-DYequals the output withouty. Thebspline,msplineandisplinecases fail onmain, pass here; thepspline,naturalsplineandrbfcases are controls that pass on both by design.test_target_aware_numerical_method_rejects_a_multi_output_target[bspline, naturalspline, rbf, ple](Preprocessor): a 2-DYraisesIncompatibleParamsError, a column vector fits. Fails onmain, passes here.test_preprocessor_defaults_reject_a_multi_output_target[default-ple, regression-preset-bspline]:Preprocessor()andPreprocessor(preset="standard", task="regression")raiseIncompatibleParamsError. Fails onmain, passes here.test_multi_output_error_suggests_a_fix_the_estimator_supports: the PLE message does not mentiontarget_aware, the B-spline message does. Fails onmain, passes here.Reproduction
Output of the script from #88 on this branch:
Checks
just test: 1492 passed, 59 skipped, 7 xfailed; coverage 93.77% (gate 90%).scripts/quickstart.pypasses.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.pyin two places. In the imports, this PR addsfrom .validation import single_targetand An invalid task such as "Regression" is silently treated as classification by the splines and selectors #93 addsfrom .parameters import validate_task. In the docstring ofBaseLocationSelector.select, this PR rewrites theyentry and An invalid task such as "Regression" is silently treated as classification by the splines and selectors #93 thetaskentry. Keep both sides in each. The code ofselectand ofPLETransformer.fitmerges cleanly (validate_taskruns first, thensingle_target), and the combined branches pass the full test suite (1523 passed, 59 skipped, 7 xfailed).uniform_knotsto the.knotsimport line inpretab/core/selectors.py, next to this PR'ssingle_targetimport: keep both. The PRs for LightGBM placement with task="classification" always uses the binary objective on raw labels #61 (fix(placement): label-encode classification targets for LightGBM #105) and CART location selector can drop the dominant split because spacing is enforced in ascending location order #70 (fix(placement): rank CART splits by impurity decrease before spacing #110) also changeselectors.pyand merge cleanly.pretab/core/validation.py(both add a helper, extend__all__and change the..exceptionsimport) and on thecore.validationimport line inpretab/encoding/numerical/ple.py. Keep both:__all__ = ["resolve_input_features", "single_target", "validate_2d_allow_nan"], importDataWarning, IncompatibleParamsError, PretabDataError, invalid_param_error, andfrom ...core.validation import resolve_input_features, single_target.pretab/core/validation.pythe same way (it addsis_polars_frameandpolars_to_pandasand extends__all__): keep both helpers and all names in__all__.pretab/expansion/spline/base.pyand merge cleanly. The explicit-knot test case here uses two knots,[-0.5, 0.5], which bothmainand the PR for B/M/I spline knot_locations are sized by output_dim and clipped to the data range instead of used as given #81 (fix(splines): honour explicit knot_locations regardless of output_dim #118) accept; with B/M/I spline knot_locations are sized by output_dim and clipped to the data range instead of used as given #81 merged in, the spline, edge-case contract andPreprocessortests pass.tests/expansion/spline/test_spline_expansions.pywith Target-aware splines trim selector knots by position, dropping the dominant split, and cap adaptive width at 15 #69 and B/M/I spline knot_locations are sized by output_dim and clipped to the data range instead of used as given #81,tests/integration/test_preprocessor.pywith Preprocessor.fit crashes on any bool column under default settings #54, Preprocessor(numerical_method="box-cox") raises at transform for values below the training minimum #59, Preprocessor.get_feature_names_out(input_features) fails after fitting on an ndarray, breaking Pipeline.get_feature_names_out() #65, Preprocessor.transform does not check n_features_in_ for ndarray input; extra columns are silently misaligned #72, Preprocessor fitted on a DataFrame rejects a same-width NumPy array at transform (and vice versa) #78 and Preprocessor rejects polars DataFrames, so pipelines with set_output(transform="polars") crash #94, andtests/integration/test_edge_case_contract.pywith Preprocessor crashes on an all-missing column at fit (numerical, one-hot) or silently drops it (int) #95, which also changes the import lines there (this PR addsIncompatibleParamsError, Preprocessor crashes on an all-missing column at fit (numerical, one-hot) or silently drops it (int) #95 addsCrossFittedTransformer). Keep both sides in each.This change was previously part of #97, which is closed in favour of one pull request per issue.