【问题标题】:Sklearn Pipeline: Get feature names after OneHotEncode In ColumnTransformerSklearn Pipeline:在 ColumnTransformer 中获取 OneHotEncode 之后的特征名称
【发布时间】:2019-07-05 21:37:04
【问题描述】:

我想在适合管道后获取功能名称。

categorical_features = ['brand', 'category_name', 'sub_category']
categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
    ('onehot', OneHotEncoder(handle_unknown='ignore'))])
    
numeric_features = ['num1', 'num2', 'num3', 'num4']
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())])

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

然后

clf = Pipeline(steps=[('preprocessor', preprocessor),
                      ('regressor', GradientBoostingRegressor())])

在适配 pandas 数据框后,我可以从中获取特征重要性

clf.steps[1][1].feature_importances_

我尝试了clf.steps[0][1].get_feature_names(),但出现错误

AttributeError: Transformer num (type Pipeline) does not provide get_feature_names.

如何从中获取功能名称?

【问题讨论】:

    标签: python scikit-learn pipeline


    【解决方案1】:

    您可以使用以下 sn-p 访问 feature_names!

    clf.named_steps['preprocessor'].transformers_[1][1]\
       .named_steps['onehot'].get_feature_names(categorical_features)
    

    使用 sklearn >= 0.21 版本,我们可以让它更简单:

    clf['preprocessor'].transformers_[1][1]['onehot']\
                       .get_feature_names(categorical_features)
    

    可重现的例子:

    import numpy as np
    import pandas as pd
    from sklearn.impute import SimpleImputer
    from sklearn.preprocessing import OneHotEncoder, StandardScaler
    from sklearn.pipeline import Pipeline
    from sklearn.compose import ColumnTransformer
    from sklearn.linear_model import LinearRegression
    
    df = pd.DataFrame({'brand': ['aaaa', 'asdfasdf', 'sadfds', 'NaN'],
                       'category': ['asdf', 'asfa', 'asdfas', 'as'],
                       'num1': [1, 1, 0, 0],
                       'target': [0.2, 0.11, 1.34, 1.123]})
    
    numeric_features = ['num1']
    numeric_transformer = Pipeline(steps=[
        ('imputer', SimpleImputer(strategy='median')),
        ('scaler', StandardScaler())])
    
    categorical_features = ['brand', 'category']
    categorical_transformer = Pipeline(steps=[
        ('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
        ('onehot', OneHotEncoder(handle_unknown='ignore'))])
    
    preprocessor = ColumnTransformer(
        transformers=[
            ('num', numeric_transformer, numeric_features),
            ('cat', categorical_transformer, categorical_features)])
    
    clf = Pipeline(steps=[('preprocessor', preprocessor),
                          ('regressor',  LinearRegression())])
    clf.fit(df.drop('target', 1), df['target'])
    
    clf.named_steps['preprocessor'].transformers_[1][1]\
       .named_steps['onehot'].get_feature_names(categorical_features)
    
    # ['brand_NaN' 'brand_aaaa' 'brand_asdfasdf' 'brand_sadfds' 'category_as'
    #  'category_asdf' 'category_asdfas' 'category_asfa']
    

    【讨论】:

    • 如何将特征重要性与所有特征名称(数字+分类)正确匹配?尤其是 OHE(handle_unknown='ignore')。
    • @Paul 在我的例子中,我将 df.columns 与 feature_names 结合起来,之后我从名称列表中删除了 categorical_features,然后将其与 feature_importances_ 结合起来。
    • 完全正确,但是如何确保它们以正确的顺序组合,以便它们与特征重要性向量匹配?似乎不是直截了当,希望优雅的代码 sn-ps
    • 合并顺序与流水线步骤相同。因此,我们可以找到特征的确切顺序。 stackoverflow.com/a/57534118/6347629 答案可能对你有用
    • 所以 StandardScaler() 没有 get_feature_names() 。我们以后是否必须将数字字段名称和一个热编码字段名称结合起来?是否有任何其他 API 可以为我们提供完整的功能名称?
    【解决方案2】:

    编辑:实际上彼得的评论答案在ColumnTransformer doc

    变换后的特征矩阵中的列顺序遵循变换器列表中列的指定顺序。除非在 passthrough 关键字中指定,否则未指定的原始特征矩阵的列将从生成的转换后的特征矩阵中删除。用 passthrough 指定的那些列被添加到转换器输出的右侧。


    为了用 Paul 在评论中提出的问题来完成 Venkatachalam 的回答,在 ColumnTransformer .get_feature_names() 方法中出现的特征名称的顺序取决于在 ColumnTransformer 实例中声明 steps 变量的顺序。

    我找不到任何文档,所以我只玩了下面的玩具示例,这让我理解了逻辑。

    from sklearn.compose import ColumnTransformer
    from sklearn.pipeline import Pipeline
    from sklearn.base import BaseEstimator, TransformerMixin
    from sklearn.preprocessing import RobustScaler
    
    class testEstimator(BaseEstimator,TransformerMixin):
        def __init__(self,string):
            self.string = string
    
        def fit(self,X):
            return self
    
        def transform(self,X):
            return np.full(X.shape, self.string).reshape(-1,1)
    
        def get_feature_names(self):
            return self.string
    
    transformers = [('first_transformer',testEstimator('A'),1), ('second_transformer',testEstimator('B'),0)]
    column_transformer = ColumnTransformer(transformers)
    steps = [('scaler',RobustScaler()), ('transformer', column_transformer)]
    pipeline = Pipeline(steps)
    
    dt_test = np.zeros((1000,2))
    pipeline.fit_transform(dt_test)
    
    for name,step in pipeline.named_steps.items():
        if hasattr(step, 'get_feature_names'):
            print(step.get_feature_names())
    

    为了获得更具代表性的示例,我添加了 RobustScaler 并将 ColumnTransformer 嵌套在 Pipeline 上。顺便说一句,你会发现我的 Venkatachalam 版本的方法来获取步骤的特征名称循环。您可以通过使用列表解析解压缩名称,将其变成一个更有用的变量:

    [i for i in v.get_feature_names() for k, v in pipeline.named_steps.items() if hasattr(v,'get_feature_names')]
    

    因此,使用 dt_test 和估计器来了解特征名称是如何构建的,以及它是如何在 get_feature_names() 中连接的。 这是另一个使用输入列输出 2 列的转换器示例:

    class testEstimator3(BaseEstimator,TransformerMixin):
        def __init__(self,string):
            self.string = string
    
        def fit(self,X):
            self.unique = np.unique(X)[0]
            return self
    
        def transform(self,X):
            return np.concatenate((X.reshape(-1,1), np.full(X.shape,self.string).reshape(-1,1)), axis = 1)
    
        def get_feature_names(self):
            return list((self.unique,self.string))
    
    dt_test2 = np.concatenate((np.full((1000,1),'A'),np.full((1000,1),'B')), axis = 1)
    
    transformers = [('first_transformer',testEstimator3('A'),1), ('second_transformer',testEstimator3('B'),0)]
    column_transformer = ColumnTransformer(transformers)
    steps = [('transformer', column_transformer)]
    pipeline = Pipeline(steps)
    
    pipeline.fit_transform(dt_test2)
    for step in pipeline.steps:
        if hasattr(step[1], 'get_feature_names'):
            print(step[1].get_feature_names())
    

    【讨论】:

      【解决方案3】:

      如果您正在寻找如何在连续管道之后访问列名,最后一个是ColumnTransformer,您可以按照以下示例访问它们:

      full_pipeline 中有两条管道genderrelevent_experience

      full_pipeline = ColumnTransformer([
          ("gender", gender_encoder, ["gender"]),
          ("relevent_experience", relevent_experience_encoder, ["relevent_experience"]),
      ])
      

      gender 管道如下所示:

      gender_encoder = Pipeline([
          ('imputer', SimpleImputer(strategy='most_frequent')),
          ("cat", OneHotEncoder())
      ])
      

      拟合full_pipeline后,可以使用以下sn -p访问列名

      full_pipeline.transformers_[0][1][1].get_feature_names()
      

      在我的例子中,输出是: array(['x0_Female', 'x0_Male', 'x0_Other'], dtype=object)

      【讨论】:

      • 这对我不起作用,因为我得到 AttributeError: 'ColumnTransformer' object has no attribute 'transformers_'
      【解决方案4】:

      Scikit-Learn 1.0 现在具有跟踪功能名称的新功能。

      from sklearn.compose import make_column_transformer
      from sklearn.impute import SimpleImputer
      from sklearn.linear_model import LinearRegression
      from sklearn.pipeline import make_pipeline
      from sklearn.preprocessing import StandardScaler
      
      # SimpleImputer does not have get_feature_names_out, so we need to add it
      # manually. This should be fixed in Scikit-Learn 1.0.1: all transformers will
      # have this method.
      # g
      SimpleImputer.get_feature_names_out = (lambda self, names=None:
                                             self.feature_names_in_)
      
      num_pipeline = make_pipeline(SimpleImputer(), StandardScaler())
      transformer = make_column_transformer(
          (num_pipeline, ["age", "height"]),
          (OneHotEncoder(), ["city"]))
      pipeline = make_pipeline(transformer, LinearRegression())
      
      
      
      df = pd.DataFrame({"city": ["Rabat", "Tokyo", "Paris", "Auckland"],
                         "age": [32, 65, 18, 24],
                         "height": [172, 163, 169, 190],
                         "weight": [65, 62, 54, 95]},
                        index=["Alice", "Bunji", "Cécile", "Dave"])
      
      
      
      pipeline.fit(df, df["weight"])
      
      
      ## get pipeline feature names
      pipeline[:-1].get_feature_names_out()
      
      
      ## specify feature names as your columns
      pd.DataFrame(pipeline[:-1].transform(df),
                   columns=pipeline[:-1].get_feature_names_out(),
                   index=df.index)
      

      【讨论】:

      • 对我来说,这导致 Estimator 编码器不提供​​ get_feature_names_out。你的意思是调用 pipeline[:-1].get_feature_names_out() 吗?
      • @AndiAnderle get_feature_names_out 并未在所有估算器上实现,请参阅 github.com/scikit-learn/scikit-learn/issues/21308 ,我使用 pipeline[:-1] 仅选择列转换器步骤。
      • 这正是我所做的 (pipeline[0].get_feature_names_out())。 pipeline[0] 是我的带有 OrdinalEncoder 和 SimpleImputer 的 ColumnTransformer。还是说上面提到的错误。
      • 你确定你有 Scikit-Learn 1.0 版本吗?
      • 是的。 1.0.1……真的很奇怪……
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-01
      • 2014-09-20
      • 2021-06-17
      • 2021-08-05
      • 2021-11-14
      • 2017-11-21
      • 2016-08-06
      相关资源
      最近更新 更多