将 ColumnTransformer 用于管道时出现 AttributeError

Giu*_*lia 11 python pipeline transformer-model pandas scikit-learn

这是我的第一个机器学习项目,也是我第一次使用 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),
])
Run Code Online (Sandbox Code Playgroud)

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

cat_pipeline.fit_transform(housing_cat)
Run Code Online (Sandbox Code Playgroud)

我收到错误:

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

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

...

ValueError:仅熊猫数据帧支持使用字符串指定列

我试过这个简化的管道,它工作正常:

new_cat_pipeline = Pipeline([
        ('imp_cat', imputer_cat_pipeline),
        ('onehot', OneHotEncoder()),
])
Run Code Online (Sandbox Code Playgroud)

但是,如果我尝试:

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),
])
Run Code Online (Sandbox Code Playgroud)

我开始遇到同样的错误。

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

Szy*_*zke 7

ColumnTransformerReturns numpy.array,因此它不能具有 column 属性(如您的错误所示)。

如果我可以建议不同的解决方案,pandas用于您的两项任务,它会更容易。

第 1 步 - 替换缺失值

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

dataframe[["PoolQC", "Alley"]].fillna("missing_value", inplace=True)
Run Code Online (Sandbox Code Playgroud)

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

dataframe[["Street", "MSZoning", "LandContour"]].fillna(
    dataframe[["Street", "MSZoning", "LandContour"]].mean(), inplace=True
)
Run Code Online (Sandbox Code Playgroud)

第 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)
Run Code Online (Sandbox Code Playgroud)

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

如果您无论如何都想使用变压器

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

pd.DataFrame(data=your_array, index=np.arange(len(your_array)), columns=["A", "B"])
Run Code Online (Sandbox Code Playgroud)

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

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

  • 好吧,如果我理解正确的话,问题不在于“ColumnTransformer”本身,而在于变压器“SimpleImputer”给出了“np.array”输出。因此,在下一个编码步骤中,我无法用名称指示列。那是对的吗?我必须使用相应的索引,不是吗? (3认同)
  • 您好,感谢您的快速回复。您的建议确实是我之前所做的:使用 fillna 来获取缺失值,使用 get_dummies/replace 来获取名义/序数变量。但是,我认为创建一个管道会更好,这样所有数据预处理都不是手动的,并且可以轻松地在具有相同变量的不同数据集上再次执行。另一方面,确实无法访问单热编码列是令人烦恼的...... (2认同)

Ant*_*ead 5

选项#2

使用make_pipeline函数

(有同样的错误,找到了这个答案,而不是找到了这个:介绍 ColumnTransformer

from sklearn.compose import make_column_transformer
from sklearn.pipeline import make_pipeline
Run Code Online (Sandbox Code Playgroud)
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']
Run Code Online (Sandbox Code Playgroud)

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),
])
Run Code Online (Sandbox Code Playgroud)

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

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

imputer_cat_pipeline = make_column_transformer(
    (make_pipeline(SimpleImputer(strategy='constant'), cat_columns_fill_miss),
)
Run Code Online (Sandbox Code Playgroud)