当使用ColumnTransformer进入管道时出现AttributeError错误

19

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

在第一步中,我想将数据框中缺失值替换为字符串“missing_value”,对于其余特征,使用最常见的值。因此,我使用ColumnTransformer结合相应列来组合这两种操作。

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

然后将这两个步骤组合成一个单一流程。

我正在使用Kaggle房价数据集,我有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' object has no attribute '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 有关,但我实际上不理解它来自哪里。在第二步中,我标识列的方式与第一步相同,因此我仍然不清楚为什么只有在第二步中会出现 Attribute Error...

4个回答

12

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)

对于有序变量及其编码,我建议您查看这个SO答案(不幸的是,在此情况下需要一些手动映射)。

如果您仍想使用转换器

使用values属性从数据框中获取np.array,将其通过管道传递并像这样从数组重新创建列和索引:

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

然而,这种方法有一个需要注意的地方;您将不知道定制创建的独热编码列的名称(管道不会为您执行此操作)。

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


2
你好,感谢您的快速回复。您建议的确是我之前所做的:使用fillna填充缺失值,并使用get_dummies / replace处理名义/序数变量。但是,我认为创建一个管道会更好,这样所有数据预处理都不是手动进行的,并且可以轻松地在具有相同变量的不同数据集上再次执行。另一方面,无法访问独热编码列确实很麻烦... - Giulia
我同意这样做会更好,但我不知道是否能以如此简洁的方式完成。 - Szymon Maszke
3
好的,如果我理解正确,问题并不在于 ColumnTransformer 本身,而在于转换器 SimpleImputer 的输出是 np.array。因此,在下一步编码中,我无法使用列名来指示列。是这样吗?我必须使用相应的索引,是吗? - Giulia
1
但是使用选项1和2,我失去了模型管道持久性的可能性... - ricoms

5

选项#2

使用make_pipeline函数

(遇到同样的错误,找到了这个答案,然后发现了这个:介绍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),
)

我尝试了您提供的代码,但它报错:TypeError: Pipeline 的最后一步应该实现fit或是字符串'passthrough'。 - curiouscheese

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

1
这不会像预期的那样工作,就像原始的 cat 列会与其独热编码列一起出现。 - ggaurav

3

每当我需要进行任何转换时,我都喜欢使用sklearn提供的FunctionTransformer,而不是直接在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')
    )
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接