【问题标题】:AttributeError when using ColumnTransformer into a pipeline在管道中使用 ColumnTransformer 时出现 AttributeError
【发布时间】:2019-06-15 08:23:33
【问题描述】:

这是我的第一个机器学习项目,也是我第一次使用 ColumnTransformer。我的目标是执行两个数据预处理步骤,并为每个步骤使用 ColumnTransformer。

在第一步中,我想用字符串 'missing_value' 替换我的数据框中的缺失值,以替换某些特征,并用最常见的值替换其余特征。因此,我使用 ColumnTransformer 将这两个操作结合起来,并将我的数据帧的相应列传递给它。

在第二步中,我想使用刚刚预处理的数据,并根据功能应用 OrdinalEncoder 或 OneHotEncoder。为此,我再次使用 ColumnTransformer。

然后我将这两个步骤合并到一个管道中。

我正在使用 Kaggle Houses Price 数据集,我有 scikit-learn 0.20 版,这是我的代码的简化版本:

cat_columns_fill_miss = ['PoolQC', 'Alley']
cat_columns_fill_freq = ['Street', 'MSZoning', 'LandContour']
cat_columns_ord = ['Street', 'Alley', 'PoolQC']
ord_mapping = [['Pave', 'Grvl'],                          # Street
               ['missing_value', 'Pave', 'Grvl'],         # Alley
               ['missing_value', 'Fa', 'TA', 'Gd', 'Ex']  # PoolQC
]
cat_columns_onehot = ['MSZoning', 'LandContour']


imputer_cat_pipeline = ColumnTransformer([
        ('imp_miss', SimpleImputer(strategy='constant'), cat_columns_fill_miss),  # fill_value='missing_value' by default
        ('imp_freq', SimpleImputer(strategy='most_frequent'), cat_columns_fill_freq),
])

encoder_cat_pipeline = ColumnTransformer([
        ('ordinal', OrdinalEncoder(categories=ord_mapping), cat_columns_ord),
        ('pass_ord', OneHotEncoder(), cat_columns_onehot),
])

cat_pipeline = Pipeline([
        ('imp_cat', imputer_cat_pipeline),
        ('cat_encoder', encoder_cat_pipeline),
])

不幸的是,当我将其应用于 Housing_cat 时,我的数据框的子集仅包含分类特征,

cat_pipeline.fit_transform(housing_cat)

我得到错误:

AttributeError: 'numpy.ndarray' 对象没有属性 'columns'

在处理上述异常的过程中,又发生了一个异常:

...

ValueError: 仅 Pandas DataFrames 支持使用字符串指定列

我已经尝试过这个简化的管道并且它工作正常:

new_cat_pipeline = Pipeline([
        ('imp_cat', imputer_cat_pipeline),
        ('onehot', OneHotEncoder()),
])

但是,如果我尝试:

enc_one = ColumnTransformer([
        ('onehot', OneHotEncoder(), cat_columns_onehot),
        ('pass_ord', 'passthrough', cat_columns_ord)
])

new_cat_pipeline = Pipeline([
        ('imp_cat', imputer_cat_pipeline),
        ('onehot_encoder', enc_one),
])

我开始遇到同样的错误。

我怀疑这个错误与第二步中使用ColumnTransformer有关,但我实际上不明白它来自哪里。我在第二步中识别列的方式与第一步相同,所以我仍然不清楚为什么只在第二步中我得到属性错误...

【问题讨论】:

    标签: python pandas scikit-learn pipeline transformer


    【解决方案1】:

    每当我进行任何转换时,我都喜欢使用FunctionTransformer sklearn 提供的功能,而不是直接在 pandas 中进行转换。这样做的原因是现在我的特征转换在新的传入数据上更通用(例如,假设你赢了,你需要使用相同的代码来预测明年的数据)。这样你就不必重新运行你的代码,你可以保存你的预处理器并调用转换。我用这样的东西

    FE_pipeline = {
    
    'numeric_pipe': make_pipeline(
        FunctionTransformer(lambda x: x.replace([np.inf, -np.inf], np.nan)),
        MinMaxScaler(),
        SimpleImputer(strategy='median', add_indicator=True),
        ),
    'oh_pipe': make_pipeline(
         FunctionTransformer(lambda x: x.astype(str)),
         SimpleImputer(strategy='constant'),
         OneHotEncoder(handle_unknown='ignore')
        )
    }
    

    【讨论】:

      【解决方案2】:

      只是在这里添加其他答案。我不是 Python 或数据科学专家,但您可以将另一个管道传递给 ColumnTransformer,以便执行您需要的操作,将多个转换器添加到列中。我来到这里寻找相同问题的答案并找到了这个解决方案。

      通过管道完成这一切使您能够更轻松地控制测试/训练数据以避免泄漏,并且还开辟了更多网格搜索的可能性。由于这些原因,我个人不喜欢在另一个答案中使用 pandas 方法,但它仍然可以正常工作。

      encoder_cat_pipeline = Pipeline([
          ('ordinal', OrdinalEncoder(categories=ord_mapping)),
          ('pass_ord', OneHotEncoder()),
      ])
      
      imputer_cat_pipeline = ColumnTransformer([
          ('imp_miss', SimpleImputer(strategy='constant'), cat_columns_fill_miss),
          ('new_pipeline', encoder_cat_pipeline, cat_columns_fill_freq)
      ])
      
      cat_pipeline = Pipeline([
          ('imp_cat', imputer_cat_pipeline),
      ])
      

      【讨论】:

      • 这不会像预期的那样工作,因为除了它的一个热编码列之外,还会有原始的 cat 列。
      【解决方案3】:

      选项 #2

      使用 ma​​ke_pipeline 功能

      (有同样的错误,找到这个答案,而不是找到这个:Introducing the ColumnTransformer

      from sklearn.compose import make_column_transformer
      from sklearn.pipeline import make_pipeline
      
      cat_columns_fill_miss = ['PoolQC', 'Alley']
      cat_columns_fill_freq = ['Street', 'MSZoning', 'LandContour']
      cat_columns_ord = ['Street', 'Alley', 'PoolQC']
      ord_mapping = [['Pave', 'Grvl'],                          # Street
                     ['missing_value', 'Pave', 'Grvl'],         # Alley
                     ['missing_value', 'Fa', 'TA', 'Gd', 'Ex']  # PoolQC
                     ]
      cat_columns_onehot = ['MSZoning', 'LandContour']
      
      
      imputer_cat_pipeline = make_column_transformer(
          (make_pipeline(SimpleImputer(strategy='constant'), cat_columns_fill_miss),
          (make_pipeline(SimpleImputer(strategy='most_frequent'), cat_columns_fill_freq),
      )
      
      encoder_cat_pipeline = make_column_transformer(
          (OrdinalEncoder(categories=ord_mapping), cat_columns_ord),
          (OneHotEncoder(), cat_columns_onehot),
      )
      
      cat_pipeline = Pipeline([
          ('imp_cat', imputer_cat_pipeline),
          ('cat_encoder', encoder_cat_pipeline),
      ])
      

      在我自己的管道中,我在列空间中没有重叠的预处理。所以我不确定转换和“外部流水线”是如何工作的。

      但是,重要部分是在 SimpleImputer 周围使用 make_pipeline 以在管道中正确使用它:

      imputer_cat_pipeline = make_column_transformer(
          (make_pipeline(SimpleImputer(strategy='constant'), cat_columns_fill_miss),
      )
      

      【讨论】:

        【解决方案4】:

        ColumnTransformer 返回numpy.array,因此它不能具有列属性(如您的错误所示)。

        如果我可以提出不同的解决方案,请使用pandas 完成您的两项任务,这样会更容易。

        第 1 步 - 替换缺失值

        要使用missing_value 字符串替换列子集中的缺失值,请使用以下命令:

        dataframe[["PoolQC", "Alley"]].fillna("missing_value", inplace=True)
        

        对于其余部分(用每列的平均值估算),这将完美地工作:

        dataframe[["Street", "MSZoning", "LandContour"]].fillna(
            dataframe[["Street", "MSZoning", "LandContour"]].mean(), inplace=True
        )
        

        第 2 步 - 一个热编码和分类变量

        pandas 提供get_dummies,它返回pandas Dataframe,与ColumnTransfomer 不同,此代码为:

        encoded = pd.get_dummies(dataframe[['MSZoning', 'LandContour']], drop_first=True)
        pd.dropna(['MSZoning', 'LandContour'], axis=columns, inplace=True)
        dataframe = dataframe.join(encoded)
        

        对于序数变量及其编码,我建议您查看at this SO answer(不幸的是,在这种情况下需要一些手动映射)。

        如果你还是想使用变压器

        使用values 属性从数据帧中获取np.array,将其通过管道并从数组中重新创建列和索引,如下所示:

        pd.DataFrame(data=your_array, index=np.arange(len(your_array)), columns=["A", "B"])
        

        不过,这种方法有一个警告;您将不知道自定义创建的单热编码列的名称(管道不会为您执行此操作)。

        此外,您可以从 sklearn 的转换对象中获取列的名称(例如,使用 categories_ 属性),但我认为它会破坏管道(如果我错了,请有人纠正我)。

        【讨论】:

        • 您好,感谢您的快速回复。您的建议确实是我之前所做的:对缺失值使用 fillna,对名义/有序变量使用 get_dummies/replace。但是,我认为创建一个管道会更好,这样所有的数据预处理都不是手动的,并且可以很容易地在具有相同变量的不同数据集上再次执行。另一方面,确实无法访问 one-hot-encoded 列很烦人……
        • 我同意它会更好,虽然我不知道如何/不知道是否可以以如此简洁的方式完成。
        • 好的,所以如果我理解正确的话,问题不在于ColumnTransformer 本身,而在于转换器SimpleImputer 作为输出np.array。因此,在下一个编码步骤中,我无法用它们的名称指示列。那是对的吗?我必须使用相应的索引,不是吗?
        • 但是使用选项 1 和 2 我失去了模型管道持久性的可能性......
        猜你喜欢
        • 2021-06-02
        • 2021-09-26
        • 1970-01-01
        • 2020-12-03
        • 2021-03-05
        • 2021-02-09
        • 1970-01-01
        • 2020-03-08
        • 2019-04-22
        相关资源
        最近更新 更多