Repository navigation
Conversation
With output_format='auto' the sparse/dense choice was re-made on every transform call from that batch's density, so the same fitted preprocessor returned a dense array for some batches and a CSR matrix for others (often single rows), breaking dense-only estimators at predict time. The choice is now made once at fit from the training output (which the inner ColumnTransformer computes anyway, since its fit runs fit_transform) and stored in the new fitted attribute output_format_; transform uses it, and output_report_ still reports each call's own density. Explicit "dense" and "sparse" are stored unchanged. Preprocessors fitted by 1.0.0 (loaded from a spec or a pickle) have no output_format_ and keep the per-call choice. Newly fitted preprocessors carry the new attribute in their state, so their fingerprint differs from one computed by 1.0.0. Fixes #73
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 #73.
With
output_format="auto",Preprocessor.transformchose between a dense array and a CSR matrix separately on every call, from that batch's density. The same fitted preprocessor therefore returned sparse output for some single rows, and dense-only estimators such asHistGradientBoostingRegressorfailed atpredictalthoughfithad succeeded.Root cause
"auto"was resolved insidecompute_output_report, whichtransformcalls with each batch's output.fitstored no decision, so the container followed the density of whatever was being transformed. Rows from PLE or one-hot blocks easily fall below the 0.3 threshold on their own.Fix
resolve_output_formatand private_densityinpretab/compose/output.py.resolve_output_formatresolves"auto"to"dense"or"sparse"from an array's density.compute_output_reportuses both; explicit formats behave as before.Preprocessor.fitcallsfit_transformon the innerColumnTransformerinstead offit, at no extra cost becauseColumnTransformer.fitrunsfit_transforminternally. It resolves the format once from the training output and stores it in the new fitted attributeoutput_format_. A dense training output goes throughnp.asarrayfirst, as intransform, because a globaltransform_output="pandas"can make the inner transformer return a DataFrame.Preprocessor.transformpassesoutput_format_tocompute_output_report, so every call returns the container chosen at fit.output_report_still reports each call's own shape and density. A preprocessor withoutoutput_format_(fitted by 1.0.0 and loaded from a spec or pickle) keeps the per-call choice.Behaviour changes
output_format="auto", everytransformreturns the container chosen atfit, including for a single row.output_format_("dense"or"sparse"). Explicit formats are stored as given.output_format_is part of the fitted state, soto_spec()andfingerprint_of a newly fitted preprocessor differ from 1.0.0 for everyoutput_format, not only"auto".Documentation
Preprocessordocstring: theoutput_formatparameter says"auto"is decided once atfitfrom the training output's density; newoutput_format_attribute.compute_output_reportand the newresolve_output_format.docs/core_concepts/outputs_and_inspection.md: theoutput_formatentry says the choice is made once atfit, from the training output, and stored inoutput_format_, so everytransformreturns the same container.Tests
test_auto_format_is_fixed_at_fit_for_dense_training_output: on the diabetes data,output_format_ == "dense", all 442 single rows come back asndarray, andoutput_report_describes the one-row call. Fails onmain, passes here.test_auto_format_is_fixed_at_fit_for_sparse_training_output: a 20-level one-hot column givesoutput_format_ == "sparse", and one row and the full frame both come back sparse. Fails onmain, passes here.test_explicit_formats_are_stored_as_resolved[dense],[sparse]: explicit formats are stored as given. Fails onmain(nooutput_format_), passes here.Reproduction
Output of the script from #73 on this branch:
Checks
just test: 1442 passed, 59 skipped, 7 xfailed; coverage 93.66% (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.self.column_transformer_.fit(X, y)line inPreprocessor.fit, so each conflicts there. Keep this PR'sfit_transformblock and apply the other change to it:fit_transform(self._column_transformer_input(X), y)for Preprocessor.fit crashes on any bool column under default settings #54 and Preprocessor routes integer column labels positionally, silently swapping columns #55, Preprocessor with one-hot encoding or preset="expanded" fails under set_config(transform_output="pandas") #76'sset_output(transform="default")line above it, and Preprocessor crashes on an all-missing column at fit (numerical, one-hot) or silently drops it (int) #95'stry/exceptaround thefit_transformcall. With Preprocessor with one-hot encoding or preset="expanded" fails under set_config(transform_output="pandas") #76 resolved this way, the full suite passes (1445 passed, 59 skipped, 7 xfailed). Once Preprocessor with one-hot encoding or preset="expanded" fails under set_config(transform_output="pandas") #76 lands, the inner transformer always returns arrays, so thenp.asarrayconversion here is redundant but harmless.tests/integration/test_output_format.pyalso gets an append/append conflict with the PR for Preprocessor with one-hot encoding or preset="expanded" fails under set_config(transform_output="pandas") #76 (fix(preprocessor): keep the internal ColumnTransformer on default output #114): keep both blocks.Preprocessor.fitandtransformnearby. Git merges both cleanly. Preprocessor.get_feature_names_out(input_features) fails after fitting on an ndarray, breaking Pipeline.get_feature_names_out() #65 also changes the fingerprint of newly fitted preprocessors, because it addsfeature_names_in_.This change was previously part of #75, which is closed in favour of one pull request per issue.