混合类型的列转换器#

本示例阐述了如何使用 ColumnTransformer 对不同的特征子集应用不同的预处理和特征提取流水线。这对于包含异构数据类型的数据集尤其方便,因为我们可能需要缩放数值特征并对类别特征进行独热编码(one-hot encode)。

在此示例中,数值数据在进行均值插补(mean-imputation)后进行标准缩放。类别数据通过 OneHotEncoder 进行独热编码,这会为缺失值创建一个新类别。我们通过使用卡方检验选择类别来进一步降低维度。

此外,我们展示了两种将列分发给特定预处理器的不同方法:按列名和按列数据类型。

最后,预处理流水线与一个简单的分类模型结合,使用 Pipeline 整合到一个完整的预测流水线中。

# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
import numpy as np

from sklearn.compose import ColumnTransformer
from sklearn.datasets import fetch_openml
from sklearn.feature_selection import SelectPercentile, chi2
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import RandomizedSearchCV, train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

np.random.seed(0)

https://www.openml.org/d/40945 加载数据

X, y = fetch_openml("titanic", version=1, as_frame=True, return_X_y=True)

# Alternatively X and y can be obtained directly from the frame attribute:
# X = titanic.frame.drop('survived', axis=1)
# y = titanic.frame['survived']

通过按名称选择列来使用 ColumnTransformer

我们将使用以下特征训练分类器

数值特征

  • age: float;

  • fare: float。

类别特征

  • embarked: 编码为字符串的类别 {'C', 'S', 'Q'};

  • sex: 编码为字符串的类别 {'female', 'male'};

  • pclass: 顺序整数 {1, 2, 3}

我们为数值数据和类别数据创建预处理流水线。请注意,pclass 既可以作为类别特征处理,也可以作为数值特征处理。

numeric_features = ["age", "fare"]
numeric_transformer = Pipeline(
    steps=[("imputer", SimpleImputer(strategy="median")), ("scaler", StandardScaler())]
)

categorical_features = ["embarked", "sex", "pclass"]
categorical_transformer = Pipeline(
    steps=[
        ("encoder", OneHotEncoder(handle_unknown="ignore", sparse_output=False)),
        ("selector", SelectPercentile(chi2, percentile=50)),
    ]
)
preprocessor = ColumnTransformer(
    transformers=[
        ("num", numeric_transformer, numeric_features),
        ("cat", categorical_transformer, categorical_features),
    ]
)

将分类器追加到预处理流水线。现在我们得到了一个完整的预测流水线。

clf = Pipeline(
    steps=[("preprocessor", preprocessor), ("classifier", LogisticRegression())]
).set_output(transform="pandas")

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

clf.fit(X_train, y_train)
print("model score: %.3f" % clf.score(X_test, y_test))
model score: 0.798

Pipeline 的 HTML 表示(显示图表)

在 Jupyter Notebook 中打印 Pipeline 时,会显示该估算器的 HTML 表示形式

clf
Pipeline(steps=[('preprocessor',
                 ColumnTransformer(transformers=[('num',
                                                  Pipeline(steps=[('imputer',
                                                                   SimpleImputer(strategy='median')),
                                                                  ('scaler',
                                                                   StandardScaler())]),
                                                  ['age', 'fare']),
                                                 ('cat',
                                                  Pipeline(steps=[('encoder',
                                                                   OneHotEncoder(handle_unknown='ignore',
                                                                                 sparse_output=False)),
                                                                  ('selector',
                                                                   SelectPercentile(percentile=50,
                                                                                    score_func=<function chi2 at 0x7841675187d0>))]),
                                                  ['embarked', 'sex',
                                                   'pclass'])])),
                ('classifier', LogisticRegression())])
在 Jupyter 环境中,请重新运行此单元格以显示 HTML 表示形式或信任 notebook。
在 GitHub 上,HTML 表示形式无法渲染,请尝试使用 nbviewer.org 加载此页面。


通过按数据类型选择列来使用 ColumnTransformer

在处理清理过的数据集时,可以使用列的数据类型自动决定是将一列视为数值特征还是类别特征来进行预处理。sklearn.compose.make_column_selector 提供了这种可能性。首先,我们只选择一个列子集来简化我们的示例。

subset_feature = ["embarked", "sex", "pclass", "age", "fare"]
X_train, X_test = X_train[subset_feature], X_test[subset_feature]

然后,我们内省有关每个列数据类型的信息。

X_train.info()
<class 'pandas.DataFrame'>
Index: 1047 entries, 1118 to 684
Data columns (total 5 columns):
 #   Column    Non-Null Count  Dtype
---  ------    --------------  -----
 0   embarked  1045 non-null   category
 1   sex       1047 non-null   category
 2   pclass    1047 non-null   int64
 3   age       841 non-null    float64
 4   fare      1046 non-null   float64
dtypes: category(2), float64(2), int64(1)
memory usage: 34.8 KB

我们可以观察到,在使用 fetch_openml 加载数据时,embarkedsex 列被标记为了 category(类别)列。因此,我们可以使用此信息将类别列分发给 categorical_transformer,而将其余列分发给 numerical_transformer

注意

在实际操作中,您必须自己处理列数据类型。如果您希望将某些列视为 category,则必须将它们转换为类别列。如果您使用的是 pandas,可以参阅其有关 Categorical data(分类数据)的文档。

from sklearn.compose import make_column_selector as selector

preprocessor = ColumnTransformer(
    transformers=[
        ("num", numeric_transformer, selector(dtype_exclude="category")),
        ("cat", categorical_transformer, selector(dtype_include="category")),
    ]
)
clf = Pipeline(
    steps=[("preprocessor", preprocessor), ("classifier", LogisticRegression())]
)


clf.fit(X_train, y_train)
print("model score: %.3f" % clf.score(X_test, y_test))
clf
model score: 0.798
Pipeline(steps=[('preprocessor',
                 ColumnTransformer(transformers=[('num',
                                                  Pipeline(steps=[('imputer',
                                                                   SimpleImputer(strategy='median')),
                                                                  ('scaler',
                                                                   StandardScaler())]),
                                                  <sklearn.compose._column_transformer.make_column_selector object at 0x784116939fd0>),
                                                 ('cat',
                                                  Pipeline(steps=[('encoder',
                                                                   OneHotEncoder(handle_unknown='ignore',
                                                                                 sparse_output=False)),
                                                                  ('selector',
                                                                   SelectPercentile(percentile=50,
                                                                                    score_func=<function chi2 at 0x7841675187d0>))]),
                                                  <sklearn.compose._column_transformer.make_column_selector object at 0x78411770d6d0>)])),
                ('classifier', LogisticRegression())])
在 Jupyter 环境中,请重新运行此单元格以显示 HTML 表示形式或信任 notebook。
在 GitHub 上,HTML 表示形式无法渲染,请尝试使用 nbviewer.org 加载此页面。


所得得分与上一个流水线的得分不完全相同,因为基于 dtype 的选择器将 pclass 列视为了数值特征,而之前是视为了类别特征。

selector(dtype_exclude="category")(X_train)
['pclass', 'age', 'fare']
selector(dtype_include="category")(X_train)
['embarked', 'sex']

在网格搜索中使用预测流水线

网格搜索也可以在 ColumnTransformer 对象中定义的各种预处理步骤上,与分类器的超参数(作为 Pipeline 的一部分)一起进行。我们将使用 RandomizedSearchCV 同时搜索数值预处理的插补策略 and 逻辑回归的正则化参数。该超参数搜索会随机选择由 n_iter 配置的固定数量的参数设置。或者,也可以使用 GridSearchCV,但这样会评估参数空间的笛卡尔积。

param_grid = {
    "preprocessor__num__imputer__strategy": ["mean", "median"],
    "preprocessor__cat__selector__percentile": [10, 30, 50, 70],
    "classifier__C": [0.1, 1.0, 10, 100],
}

search_cv = RandomizedSearchCV(clf, param_grid, n_iter=10, random_state=0)
search_cv
RandomizedSearchCV(estimator=Pipeline(steps=[('preprocessor',
                                              ColumnTransformer(transformers=[('num',
                                                                               Pipeline(steps=[('imputer',
                                                                                                SimpleImputer(strategy='median')),
                                                                                               ('scaler',
                                                                                                StandardScaler())]),
                                                                               <sklearn.compose._column_transformer.make_column_selector object at 0x784116939fd0>),
                                                                              ('cat',
                                                                               Pipeline(steps=[('encoder',
                                                                                                OneHotEncoder(handle_unknown='ignore',
                                                                                                              spars...
                                                                                                                 score_func=<function chi2 at 0x7841675187d0>))]),
                                                                               <sklearn.compose._column_transformer.make_column_selector object at 0x78411770d6d0>)])),
                                             ('classifier',
                                              LogisticRegression())]),
                   param_distributions={'classifier__C': [0.1, 1.0, 10, 100],
                                        'preprocessor__cat__selector__percentile': [10,
                                                                                    30,
                                                                                    50,
                                                                                    70],
                                        'preprocessor__num__imputer__strategy': ['mean',
                                                                                 'median']},
                   random_state=0)
在 Jupyter 环境中,请重新运行此单元格以显示 HTML 表示形式或信任 notebook。
在 GitHub 上,HTML 表示形式无法渲染,请尝试使用 nbviewer.org 加载此页面。


调用 ‘fit’ 会触发交叉验证以寻找最佳超参数组合

search_cv.fit(X_train, y_train)

print("Best params:")
print(search_cv.best_params_)
search_cv
Best params:
{'preprocessor__num__imputer__strategy': 'mean', 'preprocessor__cat__selector__percentile': 30, 'classifier__C': 100}
RandomizedSearchCV(estimator=Pipeline(steps=[('preprocessor',
                                              ColumnTransformer(transformers=[('num',
                                                                               Pipeline(steps=[('imputer',
                                                                                                SimpleImputer(strategy='median')),
                                                                                               ('scaler',
                                                                                                StandardScaler())]),
                                                                               <sklearn.compose._column_transformer.make_column_selector object at 0x784116939fd0>),
                                                                              ('cat',
                                                                               Pipeline(steps=[('encoder',
                                                                                                OneHotEncoder(handle_unknown='ignore',
                                                                                                              spars...
                                                                                                                 score_func=<function chi2 at 0x7841675187d0>))]),
                                                                               <sklearn.compose._column_transformer.make_column_selector object at 0x78411770d6d0>)])),
                                             ('classifier',
                                              LogisticRegression())]),
                   param_distributions={'classifier__C': [0.1, 1.0, 10, 100],
                                        'preprocessor__cat__selector__percentile': [10,
                                                                                    30,
                                                                                    50,
                                                                                    70],
                                        'preprocessor__num__imputer__strategy': ['mean',
                                                                                 'median']},
                   random_state=0)
在 Jupyter 环境中,请重新运行此单元格以显示 HTML 表示形式或信任 notebook。
在 GitHub 上,HTML 表示形式无法渲染,请尝试使用 nbviewer.org 加载此页面。


通过这些参数获得的内部交叉验证得分为

print(f"Internal CV score: {search_cv.best_score_:.3f}")
Internal CV score: 0.786

我们还可以将顶部的网格搜索结果以内省形式展示为 pandas DataFrame

import pandas as pd

cv_results = pd.DataFrame(search_cv.cv_results_)
cv_results = cv_results.sort_values("mean_test_score", ascending=False)
cv_results[
    [
        "mean_test_score",
        "std_test_score",
        "param_preprocessor__num__imputer__strategy",
        "param_preprocessor__cat__selector__percentile",
        "param_classifier__C",
    ]
].head(5)
mean_test_score std_test_score param_preprocessor__num__imputer__strategy param_preprocessor__cat__selector__percentile param_classifier__C
7 0.786015 0.031020 mean 30 100.0
0 0.785063 0.030498 median 30 1.0
4 0.785063 0.030498 mean 10 10.0
2 0.785063 0.030498 mean 30 1.0
3 0.783149 0.030462 mean 30 0.1


最佳超参数已用于在完整训练集上重新拟合最终模型。我们可以在未用于超参数调优的留出测试数据上评估该最终模型。

print(
    "accuracy of the best model from randomized search: "
    f"{search_cv.score(X_test, y_test):.3f}"
)
accuracy of the best model from randomized search: 0.798

脚本总运行时间: (0 分 2.118 秒)

相关示例

比较目标编码器与其他编码器

比较目标编码器与其他编码器

显示管道

显示管道

梯度提升中的分类特征支持

梯度提升中的分类特征支持

scikit-learn 1.2 发布亮点

scikit-learn 1.2 发布亮点

由 Sphinx-Gallery 生成的图库