将 ColumnTransformer 用于管道时出现 AttributeError
AttributeError when using ColumnTransformer into a pipeline
这是我的第一个机器学习项目,也是我第一次使用 ColumnTransformer。我的目标是执行两个步骤的数据预处理,并为每个步骤使用 ColumnTransformer。
第一步,我想用字符串 'missing_value' 替换我的数据框中的缺失值,并用最常见的值替换其余特征。因此,我使用 ColumnTransformer 将这两个操作结合起来,并将数据帧的相应列传递给它。
第二步,我想使用刚刚预处理过的数据,根据特征应用OrdinalEncoder或OneHotEncoder。为此,我再次使用 ColumnTransformer。
然后我将这两个步骤组合成一个管道。
我正在使用 Kaggle Houses 价格数据集,我有 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'
During handling of the above exception, another exception occurred:
...
ValueError: Specifying the columns using strings is only supported for 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有关,但我不明白它是从哪里来的。我在第二步中识别列的方式与第一步相同,所以我仍然不清楚为什么只在第二步中我得到属性错误...
ColumnTransformer
returns 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
,其中 returns pandas 数据框,与 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_
属性),但我认为这会破坏管道(如果我错了请纠正我)。
选项#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),
])
在我自己的管道中,我在 space 列中没有重叠预处理。所以我不确定,转换和 "outer pipelining" 是如何工作的。
但是,重要 部分是围绕 SimpleImputer 使用 make_pipeline 以在管道中正确使用它:
imputer_cat_pipeline = make_column_transformer(
(make_pipeline(SimpleImputer(strategy='constant'), cat_columns_fill_miss),
)
只是为了补充这里的其他答案。我不是 Python 或数据科学专家,但您可以将另一个管道传递给 ColumnTransformer
以执行您需要的操作,即向列中添加多个转换器。我来这里寻找同一个问题的答案并找到了这个解决方案。
通过管道完成这一切使您可以更轻松地控制 test/train 数据以避免泄漏,并且也打开了更多网格搜索的可能性。由于这些原因,我个人不喜欢另一个答案中的 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),
])
我喜欢使用 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')
)
}
这是我的第一个机器学习项目,也是我第一次使用 ColumnTransformer。我的目标是执行两个步骤的数据预处理,并为每个步骤使用 ColumnTransformer。
第一步,我想用字符串 'missing_value' 替换我的数据框中的缺失值,并用最常见的值替换其余特征。因此,我使用 ColumnTransformer 将这两个操作结合起来,并将数据帧的相应列传递给它。
第二步,我想使用刚刚预处理过的数据,根据特征应用OrdinalEncoder或OneHotEncoder。为此,我再次使用 ColumnTransformer。
然后我将这两个步骤组合成一个管道。
我正在使用 Kaggle Houses 价格数据集,我有 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'
During handling of the above exception, another exception occurred:
...
ValueError: Specifying the columns using strings is only supported for 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有关,但我不明白它是从哪里来的。我在第二步中识别列的方式与第一步相同,所以我仍然不清楚为什么只在第二步中我得到属性错误...
ColumnTransformer
returns 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
,其中 returns pandas 数据框,与 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_
属性),但我认为这会破坏管道(如果我错了请纠正我)。
选项#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),
])
在我自己的管道中,我在 space 列中没有重叠预处理。所以我不确定,转换和 "outer pipelining" 是如何工作的。
但是,重要 部分是围绕 SimpleImputer 使用 make_pipeline 以在管道中正确使用它:
imputer_cat_pipeline = make_column_transformer(
(make_pipeline(SimpleImputer(strategy='constant'), cat_columns_fill_miss),
)
只是为了补充这里的其他答案。我不是 Python 或数据科学专家,但您可以将另一个管道传递给 ColumnTransformer
以执行您需要的操作,即向列中添加多个转换器。我来这里寻找同一个问题的答案并找到了这个解决方案。
通过管道完成这一切使您可以更轻松地控制 test/train 数据以避免泄漏,并且也打开了更多网格搜索的可能性。由于这些原因,我个人不喜欢另一个答案中的 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),
])
我喜欢使用 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')
)
}