使用ColumnTransformer进入管道时出现AttributeError

时间:2019-01-21 22:20:16

标签: python pandas scikit-learn pipeline transformer

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

第一步,对于某些功能,我想用字符串'missing_value'替换数据框中的缺失值,对于其余功能,将其替换为最常用的值。因此,我使用ColumnTransformer结合了这两个操作,并将数据框的相应列传递给它。

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

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

我正在使用Kaggle房屋价格数据集,我的scikit学习版本为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:仅熊猫数据帧支持使用字符串指定列

我已经尝试过这种简化的管道,并且可以正常工作:

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有关,但我实际上并不了解它来自何处。我在第二步中识别列的方式与第一步中相同,因此我仍然不清楚为什么只有在第二步中才出现属性错误...

4 个答案:

答案 0 :(得分:2)

选项#2

使用 make_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),
)

答案 1 :(得分:2)

每当我进行任何转换时,我都喜欢使用 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 :(得分:0)

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,它返回了熊猫数据框,与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(不幸的是,在这种情况下,需要一些手动映射)。

如果您仍要使用变压器

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

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

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

另外,您可以从sklearn的转换对象中获取列名(例如,使用categories_属性),但我认为这样做会中断管道(如果我错了,请纠正我)。

答案 3 :(得分:0)

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

通过管道完成所有操作,使您可以更轻松地控制测试/训练数据,从而避免泄漏,并为网格搜索提供了更多可能性。由于这些原因,我个人不喜欢用熊猫的方法来回答另一个问题,但仍然可以。

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