Skip to content

fix(preprocessor): match array input by position after a named fit - #116

Open
ChrisW09 wants to merge 1 commit into
mainfrom
fix/78-array-frame-positional-match
Open

ChrisW09 wants to merge 1 commit into
mainfrom
fix/78-array-frame-positional-match

Conversation

@ChrisW09

@ChrisW09 ChrisW09 commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #78.

A Preprocessor fitted on a pandas DataFrame raised columns are missing at transform for the same data passed as a NumPy array of the same width, and one fitted on an array raised for a DataFrame. Pipelines containing a Preprocessor therefore failed at predict with NumPy-based serving, shap.KernelExplainer or df.to_numpy(), all of which scikit-learn's own transformers handle.

Root cause

transform always relabelled array input feature_0, feature_1, ... and then routed columns by name through the fitted ColumnTransformer. Those names never match the labels of a DataFrame fit, and a DataFrame's labels never match the synthetic names of an array fit.

Fix

Preprocessor.transform now converts its input with the new private Preprocessor._align_input instead of to_dataframe:

  • The fitted labels come from the inner ColumnTransformer's feature_names_in_. Labels equal to feature_0, ..., feature_{n-1} mark a fit on an array.
  • A NumPy array, and a DataFrame or dict passed after an array fit with other labels, are matched by position: they take the fitted labels, with scikit-learn's usual warning (X does not have valid feature names, but Preprocessor was fitted with feature names, or X has feature names, but Preprocessor was fitted without feature names). Numeric columns of an object array are re-inferred with infer_objects, so X.to_numpy() of a mixed frame works.
  • Input matched by position whose width differs from n_features_in_ raises PretabDataError with scikit-learn's message X has n features, but Preprocessor is expecting m features as input. This covers any array of the wrong width, which is the check the PR for Preprocessor.transform does not check n_features_in_ for ndarray input; extra columns are silently misaligned #72 (fix(preprocessor): reject NumPy input of the wrong width at transform #112) adds.
  • Any other DataFrame is matched by name, as before.

Behaviour changes

Documentation

  • Preprocessor.transform docstring: the X parameter describes matching by name and by position and the two warnings; new Raises section for positional input of the wrong width.
  • _align_input docstring.
  • docs/core_concepts/configuration.md ("NumPy array input"): a paragraph on a DataFrame fit with array input and an array fit with DataFrame input, the warnings, re-inference of object arrays, and the width error.

Tests

  • test_frame_fit_accepts_an_array_of_the_fitted_width: a Preprocessor + Ridge pipeline fitted on a mixed numeric/string frame predicts X.to_numpy() (an object array) with the warning and the same values as for X. Fails on main, passes here.
  • test_array_fit_accepts_a_frame_of_the_fitted_width: after an array fit, a renamed frame warns and gives exactly the array's output. Fails on main, passes here.
  • test_positional_input_of_the_wrong_width_is_rejected: a narrower array after a frame fit and a narrower frame after an array fit both raise PretabDataError with the scikit-learn message. Fails on main, passes here.

Reproduction

Output of the script from #78 on this branch, with the predictions in the try block printed:

sklearn: [-0.119 -0.096]
fit DataFrame, transform array: [0.112 0.12 ]
fit array, transform DataFrame: [0.112 0.12 ]

Next to scikit-learn's warning for StandardScaler, the two Preprocessor pipelines warn X does not have valid feature names, but Preprocessor was fitted with feature names and X has feature names, but Preprocessor was fitted without feature names.

Checks

  • just test: 1441 passed, 59 skipped, 7 xfailed; coverage 93.68% (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): 1428 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 #86, which is closed in favour of one pull request per issue.

Array input was always relabelled feature_0, feature_1, ... and routed
by name, so a Preprocessor fitted on a DataFrame raised 'columns are
missing' for the same data as a NumPy array (NumPy-based serving,
shap.KernelExplainer), and one fitted on an array raised for a
DataFrame.

Following scikit-learn's convention, input of the fitted width that does
not carry the fitted labels is now matched by position, with
scikit-learn's usual feature-name warning. The fitted labels come from
the internal ColumnTransformer, and numeric columns of an object array
are re-inferred. Input matched by position with a different width
raises the usual 'X has n features, but Preprocessor is expecting m
features as input.' error.

Fixes #78

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.

Preprocessor fitted on a DataFrame rejects a same-width NumPy array at transform (and vice versa)

1 participant