【问题标题】:How can I standardize only numeric variables in an sklearn pipeline?如何仅标准化 sklearn 管道中的数字变量?
【发布时间】:2018-07-18 07:25:58
【问题描述】:

我正在尝试通过 2 个步骤创建一个 sklearn 管道:

  1. 标准化数据
  2. 使用 KNN 拟合数据

但是,我的数据既有数字变量也有分类变量,我已使用 pd.get_dummies 将其转换为虚拟变量。我想标准化数字变量,但保持虚拟变量不变。我一直在这样做:

X = dataframe containing both numeric and categorical columns
numeric = [list of numeric column names]
categorical = [list of categorical column names]
scaler = StandardScaler()
X_numeric_std = pd.DataFrame(data=scaler.fit_transform(X[numeric]), columns=numeric)
X_std = pd.merge(X_numeric_std, X[categorical], left_index=True, right_index=True)

但是,如果我要创建如下管道:

pipe = sklearn.pipeline.make_pipeline(StandardScaler(), KNeighborsClassifier())

它将标准化我的 DataFrame 中的所有列。有没有办法在仅标准化数字列的同时做到这一点?

【问题讨论】:

标签: python scikit-learn


【解决方案1】:

UPD: 2021-05-10

对于sklearn >= 0.20,我们可以使用sklearn.compose.ColumnTransformer

这是small example

导入和数据加载

# Author: Pedro Morales <part.morales@gmail.com>
#
# License: BSD 3 clause

import numpy as np

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

np.random.seed(0)

# Load data from https://www.openml.org/d/40945
X, y = fetch_openml("titanic", version=1, as_frame=True, return_X_y=True)

使用ColumnTransformer进行管道感知数据预处理:

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

categorical_features = ['embarked', 'sex', 'pclass']
categorical_transformer = OneHotEncoder(handle_unknown='ignore')

preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
        ('cat', categorical_transformer, categorical_features)])

分类

# Append classifier to preprocessing pipeline.
# Now we have a full prediction pipeline.
clf = Pipeline(steps=[('preprocessor', preprocessor),
                      ('classifier', LogisticRegression())])

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))

旧答案:

假设你有以下 DF:

In [163]: df
Out[163]:
     a     b    c    d
0  aaa  1.01  xxx  111
1  bbb  2.02  yyy  222
2  ccc  3.03  zzz  333

In [164]: df.dtypes
Out[164]:
a     object
b    float64
c     object
d      int64
dtype: object

您可以找到所有数字列:

In [165]: num_cols = df.columns[df.dtypes.apply(lambda c: np.issubdtype(c, np.number))]

In [166]: num_cols
Out[166]: Index(['b', 'd'], dtype='object')

In [167]: df[num_cols]
Out[167]:
      b    d
0  1.01  111
1  2.02  222
2  3.03  333

并将StandardScaler 仅应用于这些数字列:

In [168]: scaler = StandardScaler()

In [169]: df[num_cols] = scaler.fit_transform(df[num_cols])

In [170]: df
Out[170]:
     a         b    c         d
0  aaa -1.224745  xxx -1.224745
1  bbb  0.000000  yyy  0.000000
2  ccc  1.224745  zzz  1.224745

现在您可以“一个热编码”分类(非数字)列...

【讨论】:

  • 本方案不涉及管道。
  • @nocibambi,你是对的,谢谢你的评论!我已经更新了我的答案,所以现在它展示了如何使用相对较新的ColumnTransformer 类来涉及管道;)
【解决方案2】:

我会使用FeatureUnion。然后我通常会做类似的事情,假设您也在管道内而不是之前使用 Pandas 对分类变量进行虚拟编码:

from sklearn.pipeline import Pipeline, FeatureUnion, make_pipeline
from sklearn.preprocessing import OneHotEncoder
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.neighbors import KNeighborsClassifier

class Columns(BaseEstimator, TransformerMixin):
    def __init__(self, names=None):
        self.names = names

    def fit(self, X, y=None, **fit_params):
        return self

    def transform(self, X):
        return X[self.names]

numeric = [list of numeric column names]
categorical = [list of categorical column names]

pipe = Pipeline([
    ("features", FeatureUnion([
        ('numeric', make_pipeline(Columns(names=numeric),StandardScaler())),
        ('categorical', make_pipeline(Columns(names=categorical),OneHotEncoder(sparse=False)))
    ])),
    ('model', KNeighborsClassifier())
])

您可以进一步查看Sklearn Pandas,这也很有趣。

【讨论】:

  • 嗨,马库斯,感谢您在这里发帖。那么你将如何在训练和测试数据上使用这个“管道”呢? pipe.fit(X_train,y_train)?但在这种情况下,编码器的 fit_transform 步骤将被忽略。但是如果我使用fit_transform,那么模型拟合部分会被忽略。
  • 您可以将其用作任何估算器并首先致电pipe.fit(X_train, y_train)。它将调用TransformerMixin 的所有fit_transform() 调用,然后调用最后一步估计器的fit()。如果您随后将其用于预测,它还将应用所有转换。这也可以在 model selection 类中自动工作。
  • 感谢马库斯,这很有帮助。
  • @NaveenKumar 我没有完全理解你的问题。目前Columns(names=numeric) 的输出被传递给StandardScaler(),然后与OneHotEncoder() 的输出结合并传递给KNeighborsClassifier()
  • @NaveenKumar 查看ColumnTransformer,例如this 示例。对于ColumnTransformer,请注意属性remainder,您可以使用它来模拟未命名的列会发生什么(删除它们或传递它们)。
【解决方案3】:

由于您已使用pd.get_dummies 将分类特征转换为虚拟对象,因此您无需使用OneHotEncoder。因此,您的管道应该是:

from sklearn.preprocessing import StandardScaler,FunctionTransformer
from sklearn.pipeline import Pipeline,FeatureUnion

knn=KNeighborsClassifier()

pipeline=Pipeline(steps= [
    ('feature_processing', FeatureUnion(transformer_list = [
            ('categorical', FunctionTransformer(lambda data: data[:, cat_indices])),

            #numeric
            ('numeric', Pipeline(steps = [
                ('select', FunctionTransformer(lambda data: data[:, num_indices])),
                ('scale', StandardScaler())
                        ]))
        ])),
    ('clf', knn)
    ]
)

【讨论】:

    【解决方案4】:

    另一种方法是

    import pandas as pd
    from sklearn.preprocessing import StandardScaler
    scaler = StandardScaler()
    df = pd.DataFrame()
    df['col1'] = np.random.randint(1,20,10)
    df['col2'] = np.random.randn(10)
    df['col3'] = list(5*'Y' + 5*'N')
    numeric_cols = list(df.dtypes[df.dtypes != 'object'].index)
    df.loc[:,numeric_cols] = scaler.fit_transform(df.loc[:,numeric_cols])
    

    【讨论】:

      猜你喜欢
      • 2019-05-30
      • 1970-01-01
      • 2018-07-26
      • 2021-09-21
      • 2018-04-16
      • 2018-01-09
      • 1970-01-01
      • 2017-11-10
      • 2021-05-03
      相关资源
      最近更新 更多