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
ColumnTransformercommon
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
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.
remainder='drop' is the default — unlisted columns disappear silently. Use remainder='passthrough' if that is not what you want.