MODELCATALOG

Different preprocessing per column group.

sklearn.compose

compose

Different preprocessing per column group.

1 Get data Something to practise on
2 Split & tune An honest test set
3 Prepare Into numbers a model accepts
4 Compose One object, no leakage
5 Model The algorithm itself
6 Unsupervised No labels available
7 Measure Is it any good, and why
8 Utilities Glue and extension

What it is for

The subpackage people find late and wish they had known earlier. Real tabular data has numeric columns, categorical columns and free-text columns that each need entirely different treatment. ColumnTransformer applies all of it in one object that still behaves like a single transformer.

Contents

ColumnTransformer common Route column groups to different transformers
make_column_transformer Same, auto-named
make_column_selector Select columns by dtype or name pattern rather than listing them
TransformedTargetRegressor Transform y too — log a skewed target, invert automatically on predict

Worked examples 4

Runnable, with the arguments that change the outcome called out. Output shapes are shown as comments where they help.

sklearn.compose

from sklearn.compose import ColumnTransformer, make_column_selector
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.ensemble import HistGradientBoostingClassifier

numeric = Pipeline([("impute", SimpleImputer(strategy="median")),
                    ("scale",  StandardScaler())])

categorical = Pipeline([("impute", SimpleImputer(strategy="most_frequent")),
                        ("onehot", OneHotEncoder(handle_unknown="ignore"))])

pre = ColumnTransformer([
    ("num", numeric,     make_column_selector(dtype_include="number")),
    ("cat", categorical, make_column_selector(dtype_include="object")),
    ("txt", TfidfVectorizer(min_df=3), "complaint_text"),   # ONE column: a
                                                            # string, not a list
])

model = Pipeline([("pre", pre), ("clf", HistGradientBoostingClassifier())])
model.fit(X_train, y_train)

Numeric, categorical and text in one object

The shape real tabular data needs. Note that text columns take a string, not a list — passing a list to TfidfVectorizer is a common and confusing error.

from sklearn.compose import ColumnTransformer, make_column_selector
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.ensemble import HistGradientBoostingClassifier

numeric = Pipeline([("impute", SimpleImputer(strategy="median",
                                             add_indicator=True)),
                    ("scale",  StandardScaler())])

categorical = Pipeline([("impute", SimpleImputer(strategy="most_frequent")),
                        ("onehot", OneHotEncoder(handle_unknown="ignore",
                                                 min_frequency=0.01))])

pre = ColumnTransformer(
    transformers=[
        ("num", numeric,     make_column_selector(dtype_include="number")),
        ("cat", categorical, make_column_selector(dtype_include="object")),
        ("txt", TfidfVectorizer(min_df=3), "complaint_text"),   # STRING
    ],
    remainder="drop",          # the default. Unlisted columns VANISH.
    verbose_feature_names_out=True,
)

model = Pipeline([("pre", pre),
                  ("clf", HistGradientBoostingClassifier(max_iter=300))])
model.fit(X_train, y_train)

print(model.named_steps["pre"].get_feature_names_out()[:8])
# ['num__income' 'num__age' 'cat__product_current' 'txt__overdraft' ...]

Get DataFrames back instead of bare arrays

One global setting that makes debugging a ColumnTransformer dramatically easier — you can see which column is which.

import sklearn
sklearn.set_config(transform_output="pandas")     # 1.2+

out = pre.fit_transform(X_train, y_train)
print(type(out))            # <class 'pandas.core.frame.DataFrame'>
print(out.columns[:6].tolist())
print(out.head(3))

# Without this you get a numpy array and have to reconstruct which
# column means what from get_feature_names_out(). With it, the names
# travel with the data.
#
# Note: does not apply to transformers that must return sparse output,
# such as TfidfVectorizer on a large corpus.

Transform the target as well

For skewed regression targets. It logs y before fitting and exponentiates predictions back automatically, so you never hold the inverse transform in your head.

from sklearn.compose import TransformedTargetRegressor
from sklearn.ensemble import HistGradientBoostingRegressor
import numpy as np

# Loan balances and claim amounts are right-skewed; models fit them
# better in log space, but you want predictions in currency.
model = TransformedTargetRegressor(
    regressor=HistGradientBoostingRegressor(),
    func=np.log1p,          # applied to y before fitting
    inverse_func=np.expm1,  # applied to predictions on the way out
)

model.fit(X_train, y_train)
print(model.predict(X_test)[:5])     # in the original units

Worth knowing

  1. Text transformers take a single column name as a string. Numeric ones take a list. Passing a list to TfidfVectorizer is a common and confusing error.

  2. remainder='drop' is the default — unlisted columns disappear silently. Use remainder='passthrough' if that is not what you want.