Repository navigation
Preprocessor crashes on an all-missing column at fit (numerical, one-hot) or silently drops it (int) #95
Description
Description
A column that is entirely missing at fit breaks the default Preprocessor. This is realistic: an optional field can be empty in a training window, a sample or a cross-validation training fold.
- Numerical column:
fitcrashes inside scikit-learn withFound array with 0 feature(s). This happens with the default PLE and also with minmax, the splines, the feature maps and every other numerical method except"none", which silently drops the column as described for"int"below. The error namesMinMaxScalerbecause the defaultscaling="minmax"step is the first to receive the empty array; withscaling=Nonethe method itself fails the same way. - Categorical column with the default
"int"encoding: the column silently disappears from the output,output_dims_, the dict blocks andget_feature_lineage(). It is still listed incategorical_features_and counted inn_features_in_. get_feature_info()andPreprocessor(verbose=2).fitthen raiseIndexError.categorical_method="one-hot": crashes like the numerical case.missing_policy="separate_state": a categorical column keeps only the__missingcolumn and its representation disappears; a numerical column crashes as above.
Affected code
pretab/compose/factory.py:121andpretab/compose/factory.py:198: the imputers are built with scikit-learn's defaultkeep_empty_features=False, so an all-missing column is dropped and the next step receives an(n, 0)array.pretab/compose/inspection.py:163:step.mapping_[columns.index(feature_name)]on an encoder fitted on zero columns.docs/representations/choosing_a_method.md:84: "PreTab is explicit about degenerate inputs rather than failing silently."
Reproduction
import warnings
import numpy as np, pandas as pd
from pretab import Preprocessor
warnings.simplefilter("ignore") # hides sklearn's "Skipping features without any observed values" UserWarning
rng = np.random.default_rng(0)
n = 100
y = rng.normal(size=n)
age, city = rng.normal(40, 10, n), rng.choice(["a", "b", "c"], n)
X_num = pd.DataFrame({"age": age, "income": np.nan, "city": city})
try:
Preprocessor().fit(X_num, y)
except Exception as e:
print("numerical all-NaN column ->", type(e).__name__ + ":", e)
X_cat = pd.DataFrame({"age": age, "city": city, "segment": pd.Series([np.nan] * n, dtype=object)})
pre = Preprocessor().fit(X_cat, y)
print("categorical all-NaN column -> categorical_features_:", pre.categorical_features_,
"| output_dims_:", pre.output_dims_, "| blocks:", sorted(pre.transform(X_cat, return_array=False)))
try:
pre.get_feature_info(verbose=False)
except Exception as e:
print(" get_feature_info() ->", type(e).__name__ + ":", e)
try:
Preprocessor(categorical_method="one-hot").fit(X_cat, y)
except Exception as e:
print("one-hot, all-NaN column ->", type(e).__name__ + ":", e)Output (PreTab 1.0.0 (6bacaba), Python 3.12, numpy 2.5.3, pandas 2.3.3, scikit-learn 1.9.1, scipy 1.18.1):
numerical all-NaN column -> ValueError: Found array with 0 feature(s) (shape=(100, 0)) while a minimum of 1 is required by MinMaxScaler.
categorical all-NaN column -> categorical_features_: ['city', 'segment'] | output_dims_: {'age': 7, 'city': 1} | blocks: ['cat_city', 'num_age']
get_feature_info() -> IndexError: list index out of range
one-hot, all-NaN column -> ValueError: Found array with 0 feature(s) (shape=(100, 0)) while a minimum of 1 is required.
Expected behavior
Every detected column keeps a stable output block, and the metadata (n_features_in_, *_features_, output_dims_, names, lineage, get_feature_info) agrees with the output. Alternatively, fit raises a clear PretabDataError naming the column.
The docs promise that "PreTab is explicit about degenerate inputs rather than failing silently".
Actual behavior
An opaque scikit-learn error, or a column that is silently dropped while the metadata still lists it.
Impact
A crash on the default path for a common data situation. Where it does not crash, the output width depends on whether a column happened to be observed in the training rows.
Root cause
SimpleImputer drops features without any observed value unless keep_empty_features=True, and each per-column pipeline holds exactly one column.
Suggested fix direction
- Build the imputers with
keep_empty_features=True, so the column is filled with a constant and keeps its block. Warn atfit, naming the column. - Or check for columns without observed values at
fitand raise a typed error. - Either way, make
build_feature_inforobust to an empty block.
Related issues
- add_missing_indicator / impute_with_indicator feeds the indicator through the scaler and basis; lineage crashes #62: the same imputer steps (
factory.py:121and:198) also feed the missing indicator through the scaler and the basis.
Activity
docs: list the methods that cannot fit an all-missing column without …
[-]A column with no observed values at fit crashes the Preprocessor (numerical, one-hot) or silently disappears from the output (int)[/-][+]Preprocessor crashes on an all-missing column at fit (numerical, one-hot) or silently drops it (int)[/+]