应用sklearn.compose.ColumnTransformer后保留列顺序
问题描述
我正在使用sklearn
库中的Pipeline
和ColumnTransformer
模块对我的数据集执行功能工程。
数据集最初如下所示:
日期 | Date_挡路_Num | shop_id | item_id | Item_Price |
---|---|---|---|---|
02.01.2013 | 0 | 59 | 22154 | 999.00 |
03.01.2013 | 0 | 25 | 2552 | 899.00 |
05.01.2013 | 0 | 25 | 2552 | 899.00 |
06.01.2013 | 0 | 25 | 2554 | 1709.05 |
15.01.2013 | 0 | 25 | 2555 | 1099.00 |
$> data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2935849 entries, 0 to 2935848
Data columns (total 6 columns):
# Column Dtype
--- ------ -----
0 date object
1 date_block_num object
2 shop_id object
3 item_id object
4 item_price float64
dtypes: float64(2), int64(3), object(1)
memory usage: 134.4+ MB
然后我有以下转换:
num_column_transformer = ColumnTransformer(
transformers=[
("std_scaler", StandardScaler(), make_column_selector(dtype_include=np.number)),
],
remainder="passthrough"
)
num_pipeline = Pipeline(
steps=[
("percent_item_cnt_day_per_shop", PercentOverTotalAttributeWholeAdder(
attribute_percent_name="shop_id",
attribute_total_name="item_cnt_day",
new_attribute_name="%_item_cnt_day_per_shop")
),
("percent_item_cnt_day_per_item", PercentOverTotalAttributeWholeAdder(
attribute_percent_name="item_id",
attribute_total_name="item_cnt_day",
new_attribute_name="%_item_cnt_day_per_item")
),
("percent_sales_per_shop", SalesPerAttributeOverTotalSalesAdder(
attribute_percent_name="shop_id",
new_attribute_name="%_sales_per_shop")
),
("percent_sales_per_item", SalesPerAttributeOverTotalSalesAdder(
attribute_percent_name="item_id",
new_attribute_name="%_sales_per_item")
),
("num_column_transformer", num_column_transformer),
]
)
前四个Transformers
创建四个新的不同数值变量,最后一个StandardScaler
应用于数据集的所有数值。
执行后,我得到以下数据:
0 | %1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
-0.092652 | -0.765612 | -0.173122 | -0.756606 | -0.379775 | 02.01.2013 | 0 | 59 | 22154 |
-0.092652 | 1.557684 | -0.175922 | 1.563224 | -0.394319 | 03.01.2013 | 0 | 25 | 2552 |
-0.856351 | 1.557684 | -0.175922 | 1.563224 | -0.394319 | 05.01.2013 | 0 | 25 | 2552 |
-0.092652 | 1.557684 | -0.17613 | 1.563224 | -0.396646 | 06.01.2013 | 0 | 25 | 2554 |
-0.092652 | 1.557684 | -0.173278 | 1.563224 | -0.380647 | 15.01.2013 | 0 | 25 | 2555 |
我希望有以下输出:
日期 | Date_挡路_Num | shop_id | item_id | Item_Price | %_item_cnt_day_per_shop | %_item_cnt_day_per_item | %_Sales_Per_shop | %_Sales_Per_Item |
---|---|---|---|---|---|---|---|---|
02.01.2013 | 0 | 59 | 22154 | -0.092652 | -0.765612 | -0.173122 | -0.756606 | -0.379775 |
03.01.2013 | 0 | 25 | 2552 | -0.092652 | 1.557684 | -0.175922 | 1.563224 | -0.394319 |
05.01.2013 | 0 | 25 | 2552 | -0.856351 | 1.557684 | -0.175922 | 1.563224 | -0.394319 |
06.01.2013 | 0 | 25 | 2554 | -0.092652 | 1.557684 | -0.17613 | 1.563224 | -0.396646 |
15.01.2013 | 0 | 25 | 2555 | -0.092652 | 1.557684 | -0.173278 | 1.563224 | -0.380647 |
5
、6
、7
和8
对应于原始数据集中的前四列。例如,我不知道item_price
功能在输出表中的位置。
- 如何保留列顺序和名称?之后,我要对类别变量进行功能工程,并且我的转换器使用功能列名。
- 我是否正确使用了Scikit-Learn API?
解决方案
处理ColumnTransformer
时需要注意一点,在doc中报告如下:
转换功能矩阵中列的顺序遵循变压器列表中列的指定顺序。
这就是ColumnTransformer
实例将事情搞得一团糟的原因。实际上,请考虑下面这个与您的设置类似的简化示例:
import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer, make_column_selector
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
df = pd.DataFrame({
'date': ['02.01.2013', '03.01.2013', '05.01.2013', '06.01.2013', '15.01.2013'],
'date_block_num': ['0', '0', '0', '0', '0'],
'shop_id': ['59', '25', '25', '25', '25'],
'item_id': ['22514', '2252', '2252', '2254', '2255'],
'item_price': [999.00, 899.00, 899.00, 1709.05, 1099.00]})
ct = ColumnTransformer([
('std_scaler', StandardScaler(), make_column_selector(dtype_include=np.number))],
remainder='passthrough')
pd.DataFrame(ct.fit_transform(df), columns=ct.get_feature_names_out())
正如您可能注意到的,转换后的数据帧中的第一列原来是数字列,即经过缩放的列(转换器列表中的第一列)。
相反,下面的示例说明了如何通过在传递所有字符串变量之后推迟对数值变量的缩放来绕过此问题,从而确保按所需顺序获取列的可能性:
ct = ColumnTransformer([
('pass', 'passthrough', make_column_selector(dtype_include=object)),
('std_scaler', StandardScaler(), make_column_selector(dtype_include=np.number))
])
pd.DataFrame(ct.fit_transform(df), columns=ct.get_feature_names_out())
为了完成图片,这里尝试重现您的Pipeline(尽管自定义变压器肯定与您的稍有不同):
from sklearn.base import BaseEstimator, TransformerMixin
class PercentOverTotalAttributeWholeAdder(BaseEstimator, TransformerMixin):
def __init__(self, attribute_percent_name='shop_id', new_attribute_name='%_item_cnt_day_per_shop'):
self.attribute_percent_name = attribute_percent_name
self.new_attribute_name = new_attribute_name
def fit(self, X, y=None):
return self
def transform(self, X, y=None):
df[self.new_attribute_name] = df.groupby(by=self.attribute_percent_name)[self.attribute_percent_name].transform('count') / df.shape[0]
return df
ct_pipe = ColumnTransformer([
('pass', 'passthrough', make_column_selector(dtype_include=object)),
('std_scaler', StandardScaler(), make_column_selector(dtype_include=np.number))
], verbose_feature_names_out=False)
pipe = Pipeline([
('percent_item_cnt_day_per_shop', PercentOverTotalAttributeWholeAdder(
attribute_percent_name='shop_id',
new_attribute_name='%_item_cnt_day_per_shop')
),
('percent_item_cnt_day_per_item', PercentOverTotalAttributeWholeAdder(
attribute_percent_name='item_id',
new_attribute_name='%_item_cnt_day_per_item')
),
('column_trans', ct_pipe),
])
pd.DataFrame(pipe.fit_transform(df), columns=pipe[-1].get_feature_names_out())
最后,请注意verbose_feature_names_out=False
参数可确保转换后的数据帧的列名称不会显示引用ColumnTransformer
中不同转换器的前缀。
相关文章