Repository navigation
Conversation
Boolean columns are detected as categorical, but the categorical pipeline starts with a SimpleImputer, which rejects the bool dtype, so any frame (or NumPy array) with a plain bool column failed to fit under the default settings, every categorical_method and every preset. Boolean columns are now cast to object (True / False) before they reach the internal ColumnTransformer, so they are encoded like any other binary categorical column. A missing value of pandas' nullable boolean dtype becomes NaN, the missing marker the imputer and the encoders recognize. The caller's frame is left unchanged. Fixes #54
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 #54.
Any DataFrame or NumPy array with a plain
boolcolumn madePreprocessor.fitraiseValueError: SimpleImputer does not support data with dtype bool. This happened under the default settings, everycategorical_method, every preset, and the imputing missing policies.Root cause
detect_column_typesclassifiesbool(dtype kind"b") as categorical. The categorical pipeline starts withSimpleImputer(strategy="most_frequent")whenever imputation is enabled, which is the default, andSimpleImputerrejects thebooldtype.Fix
compose.feature_detection.bool_columns_as_objectcasts boolean columns (booland pandas' nullableboolean) toobjectwith the valuesTrue/False, so they are encoded like any other binary categorical column. A missing value of the nullablebooleandtype becomesNaN, the missing marker the imputer and the encoders recognize. The function returns a shallow copy, so the caller's frame is unchanged. A frame without boolean columns is returned as is.Preprocessor._column_transformer_inputapplies it.fitandtransformpass their frame through it to the internalColumnTransformer. Column-type detection still sees the original dtypes.Behaviour changes
categorical_method="none"now passes a boolean column through as the Python objectsTrue/False. The block holds those values, and the stacked output becomes anobjectarray:bool,"none"only fitted onmainwithcategorical_imputation=Noneormissing_policy="propagate". The column was then upcast to1.0/0.0in a float array.booleanwith the default imputer,mainreturned float1.0/0.0. Apd.NAthat is not imputed (categorical_imputation=Noneormissing_policy="propagate") mademainraise. It now passes through asNaN.dtype(for exampledtype=np.float32), the output is still cast to1.0/0.0. A frame with any string categorical column already produced anobjectarray under"none".booleancolumns change fromcat_flag_0.0/cat_flag_1.0tocat_flag_False/cat_flag_True. Onmain, the imputer had turned the column into floats. Without imputation, the missing category is stillcat_flag_nan. The values are unchanged.pd.NAin a nullablebooleancolumn is now treated as missing everywhere. Withcategorical_method="int"andmissing_policy="propagate"orcategorical_imputation=None,mainraisedTypeError: boolean value of NA is ambiguous. The value now gets the missing code0.mainkeep identical names and values. That coversint,one-hotandonehot_from_ordinalon plainboolwithcategorical_imputation=Noneormissing_policy="propagate"/"error", andint/onehot_from_ordinalon nullableboolean.Documentation
FalseandTrue.Preprocessordocstring, Notes:booland nullablebooleancolumns are binary categoricals, and a nullable missing value is treated as missing.detect_column_typesdocstring: non-numeric dtypes, includingbooland nullableboolean, are always categorical.bool_columns_as_objectdocstring.Tests
test_bool_column_is_encoded_as_a_binary_categorical: 11 cases, covering every failing configuration from the issue:categorical_method"one-hot","onehot_from_ordinal"and"none";"standard","expanded"and"adaptive";missing_policy"impute","impute_with_indicator"and"separate_state";treat_all_integers_as_numerical=True.For each, the test checks the exact trailing feature names,
categorical_features_ == ["flag"]and the transform width. Fails onmain, passes here.test_bool_column_integer_codes_follow_the_values: withcategorical_method="int"the codes are exactlyFalse -> 1andTrue -> 2. Fails onmain, passes here.test_nullable_boolean_column_with_missing_values_is_imputed: one-hot names arecat_flag_False/cat_flag_True, and thepd.NArow is imputed toTrue. Fails onmain(namescat_flag_0.0/cat_flag_1.0), passes here.test_nullable_boolean_missing_value_gets_the_missing_code_without_imputation: undermissing_policy="propagate", the codes are[2, 1, 0, 1, 2, 2]withpd.NAmapped to0. Fails onmain(TypeError), passes here.test_bool_numpy_array_is_supported: aboolndarray fits and transforms. Fails onmain, passes here.test_bool_columns_as_object_casts_only_boolean_columns: checks the dtypes and values after the cast (NaNforpd.NA) and that the input keeps its dtypes. Fails onmain(the function does not exist), passes here.test_bool_columns_as_object_returns_frame_without_bool_columns_unchanged: a frame without boolean columns is returned as the same object. Fails onmain(the function does not exist), passes here.Reproduction
Output of the script from #54 on this branch. It runs under the default settings, under each configuration the issue lists as also failing, and on a NumPy
boolarray. Each line shows the categorical feature names (all names for the array):Checks
just test: 1455 passed, 59 skipped, 7 xfailed; coverage 93.71% (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
Base:
main.The PR for Preprocessor routes integer column labels positionally, silently swapping columns #55 (fix(preprocessor): route columns to the ColumnTransformer by label #100) adds the same
Preprocessor._column_transformer_inputhelper in the same place, and both PRs routefitandtransformthrough it, so those call lines merge cleanly. Conflicts:return with_string_labels(bool_columns_as_object(X))and merge both docstring sentences.feature_detectionimport line inpreprocessor.py, andfeature_detection.__all__.to_dataframe.tests/compose/test_feature_detection.py.Keep both sides in each. With the conflicts resolved this way, the full test suite passes (1464 passed, 59 skipped, 7 xfailed).
The PRs for output_format="auto" chooses sparse vs dense per transform batch, breaking dense-only estimators at predict #73 (fix(preprocessor): resolve output_format='auto' once per fit #113), 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) and Preprocessor crashes on an all-missing column at fit (numerical, one-hot) or silently drops it (int) #95 (fix(preprocessor): keep a block for columns with no observed value at fit #130) change the
self.column_transformer_.fit(...)line infit. Keep their change, and passself._column_transformer_input(X)instead ofXto theColumnTransformer. Preprocessor crashes on an all-missing column at fit (numerical, one-hot) or silently drops it (int) #95 also adds a Notes paragraph next to this PR's boolean paragraph: keep both.The PR for Preprocessor fitted on a DataFrame rejects a same-width NumPy array at transform (and vice versa) #78 (fix(preprocessor): match array input by position after a named fit #116) adds
_align_inputat the place where this PR adds_column_transformer_input: keep both methods.The PR for Preprocessor rejects polars DataFrames, so pipelines with set_output(transform="polars") crash #94 (fix(preprocessor): accept polars DataFrames as input #129) adds
has_column_labelsnext tobool_columns_as_objectinfeature_detection(in__all__and as a new function): keep both.This PR and the PRs for Preprocessor(numerical_method="box-cox") raises at transform for values below the training minimum #59 (fix(preprocessor): keep box-cox finite below the training minimum #104), Preprocessor.get_feature_names_out(input_features) fails after fitting on an ndarray, breaking Pipeline.get_feature_names_out() #65 (fix(preprocessor): accept input_features after fitting on a NumPy array #108), 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), Preprocessor fitted on a DataFrame rejects a same-width NumPy array at transform (and vice versa) #78 (fix(preprocessor): match array input by position after a named fit #116), A 2-D y (multi-output target) crashes the B/M/I splines even when unsupervised, and target-aware methods with unrelated errors #88 (fix(splines): ignore y when unsupervised and reject a multi-output target #123) and Preprocessor rejects polars DataFrames, so pipelines with set_output(transform="polars") crash #94 (fix(preprocessor): accept polars DataFrames as input #129) all append tests at the end of
tests/integration/test_preprocessor.py, so whichever lands later gets an append/append conflict there: keep both blocks.None / pd.NA categorical missing values are not treated as missing (nullable dtypes crash, None becomes a category) #64 (
None/pd.NAin categorical columns) is still open. This PR only turnspd.NAin nullablebooleancolumns intoNaN, and the fix for None / pd.NA categorical missing values are not treated as missing (nullable dtypes crash, None becomes a category) #64 should agree withbool_columns_as_object.This change was previously part of #75, which is closed in favour of one pull request per issue.