应用sklearn.compose.ColumnTransformer后保留列顺序

16

我正在使用 sklearn 库中的 PipelineColumnTransformer 模块对我的数据集进行特征工程。

最初的数据集如下:

日期 日期块编号 商店编号 商品编号 商品价格
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 date_block_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
2个回答

19

在处理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())

enter image description here

你可能会注意到,转换后的数据框中第一列是数字列,即经过缩放的列(也是变换器列表中的第一列)。

相反,以下是一个例子,说明如何通过在所有字符串变量经过后再进行数值变量的缩放来绕过此问题,并确保获得所需的列顺序:

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())

enter image description here

为了完整呈现,这里尝试重现您的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())

enter image description here

作为最后一条备注,需要注意 verbose_feature_names_out=False 参数确保转换后的数据框中的列名不显示前缀,这些前缀是指在 ColumnTransformer 中的不同转换器。

2

使用scikit-learn 1.2.1进行回答

scikit-learn 1.2中,可以将ColumnTransformer的输出设置为pandas数据框,避免在第二步中进行转换。此外,在@amiola提出的答案中,ColumnTransformer使用一个passthrough阶段来保留字符串类型列相对于数字列的顺序,但这仅在所有字符串类型列位于数字列之前时才有效。为了说明这一点,我使用相同的示例将shop_id列转换为数字:

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([
         ('pass', 'passthrough', make_column_selector(dtype_include=object)),
         ('std_scaler', StandardScaler(), make_column_selector(dtype_include=np.number))
                      ]).set_output(transform='pandas')
out_df = ct.fit_transform(df)
out_df
pass__date pass__date_block_num pass__item_id std_scaler__shop_id std_scaler__item_price
0 02.01.2013 0 22514 2.0 -0.402369
1 03.01.2013 0 2252 -0.5 -0.732153
2 05.01.2013 0 2252 -0.5 -0.732153
3 06.01.2013 0 2254 -0.5 1.939261
4 15.01.2013 0 2255 -0.5 -0.072585

可以看到,shop_id列被移动到了最后,出于与amiola答案相同的原因(即按照ColumnTrasnformer中的转换顺序重新排序列)。为了解决这个问题,您可以在转换后使用verbose_feature_names_out设置为False重新 排序数据框列 以保留相同的起始列名称(请注意,这些名称必须是唯一的,请参见文档)。也没有必要创建特定的通过步骤。

ct = ColumnTransformer([
    ('std_scaler', StandardScaler(), make_column_selector(dtype_include=np.number))],
     remainder='passthrough',
     verbose_feature_names_out=False).set_output(transform='pandas')

out_df = ct.fit_transform(df)
out_df = out_df[df.columns]
out_df
date date_block_num shop_id item_id item_price
0 02.01.2013 0 2.0 22514 -0.402369
1 03.01.2013 0 -0.5 2252 -0.732153
2 05.01.2013 0 -0.5 2252 -0.732153
3 06.01.2013 0 -0.5 2254 1.939261
4 15.01.2013 0 -0.5 2255 -0.072585

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