pandas 应用sklearn. composite.ColumnTransformer后保留列顺序

bvhaajcl  于 2023-02-14  发布在  其他
关注(0)|答案(2)|浏览(401)

我正在使用sklearn库中的PipelineColumnTransformer模块对我的数据集执行特征工程。
数据集最初如下所示:
| 日期|日期块编号|商店标识|项目标识|项目_价格|
| - ------|- ------|- ------|- ------|- ------|
| 二○一三年一月二日|无|五十九|小行星22154|九九九元|
| 二○一三年一月三日|无|二十五|小行星255|八百九十九元|
| 二○一三年一月五日|无|二十五|小行星255|八百九十九元|
| 二○一三年一月六日|无|二十五|小行星2554|小行星1709.05|
| 二○一三年一月十五日|无|二十五|小行星255|一千零九十九元|

$> 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应用于数据集的所有数值。
执行后,我得到以下数据:
| 无|1个|第二章|三个|四个|五个|六个|七|八个|
| - ------|- ------|- ------|- ------|- ------|- ------|- ------|- ------|- ------|
| -0.092652|-0.765612|-0.173122|-0.756606|-0.379775|二○一三年一月二日|无|五十九|小行星22154|
| -0.092652|一千五百五十七六百八十四|-0.175922|一点五六三二二四|-0.394319|二○一三年一月三日|无|二十五|小行星255|
| -0.856351|一千五百五十七六百八十四|-0.175922|一点五六三二二四|-0.394319|二○一三年一月五日|无|二十五|小行星255|
| -0.092652|一千五百五十七六百八十四|-0.17613|一点五六三二二四|-0.396646|二○一三年一月六日|无|二十五|小行星2554|
| -0.092652|一千五百五十七六百八十四|-0.173278|一点五六三二二四|-0.380647|二○一三年一月十五日|无|二十五|小行星255|
我希望有以下输出:
| 日期|日期块编号|商店标识|项目标识|项目_价格|%_项目_cnt_天_每个_商店|%_项目_计数_天_每_项目|每个店铺的销售额百分比|每件商品的销售额%|
| - ------|- ------|- ------|- ------|- ------|- ------|- ------|- ------|- ------|
| 二○一三年一月二日|无|五十九|小行星22154|-0.092652|-0.765612|-0.173122|-0.756606|-0.379775|
| 二○一三年一月三日|无|二十五|小行星255|-0.092652|一千五百五十七六百八十四|-0.175922|一点五六三二二四|-0.394319|
| 二○一三年一月五日|无|二十五|小行星255|-0.856351|一千五百五十七六百八十四|-0.175922|一点五六三二二四|-0.394319|
| 二○一三年一月六日|无|二十五|小行星2554|-0.092652|一千五百五十七六百八十四|-0.17613|一点五六三二二四|-0.396646|
| 二○一三年一月十五日|无|二十五|小行星255|-0.092652|一千五百五十七六百八十四|-0.173278|一点五六三二二四|-0.380647|
如您所见,输出中的列5678对应于原始数据集中的前四列,例如,我不知道item_price特征在输出表中的位置。
1.* * 如何保留列顺序和名称?**之后,我想对分类变量进行特性工程,我的Transformers将使用特性列名。
1.我是否正确使用了Scikit-Learn API?

tkclm6bt

tkclm6bt1#

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

正如您可能注意到的,转换后的 Dataframe 中的第一列是 numeric 列,即经历缩放的列(并且是 * transformers列表中的第一列 *)。
相反,这里有一个例子,说明如何通过在传递完所有字符串变量之后推迟对数值变量的缩放,从而确保按所需顺序获得列的可能性,来绕过此类问题:

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参数确保转换后的 Dataframe 的列的名称不显示引用ColumnTransformer中的不同转换器的前缀。

hujrc8aj

hujrc8aj2#

使用scikit-learn 1.2.1回答问题

在scikit-learn 1.2中,可以将ColumnTransformer的输出设置为panda Dataframe ,从而避免了第二步的转换。除此之外,在answer proposed by @amiola中,ColumnTransformer使用了一个直通阶段来保持字符串类型列相对于数值列的顺序。但这只有在所有字符串类型的列都在数字之前时才有效,为了说明这一点,我使用了相同的例子将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

| | 通过日期|通过日期块编号|通过__项目_id|标准_定标器__车间_ID|标准定标器项目价格|
| - ------|- ------|- ------|- ------|- ------|- ------|
| 无|二○一三年一月二日|无|小行星22514|2.0版| -0.402369 |
| 1个|二○一三年一月三日|无|小行星225|-0.5| -0.732153 |
| 第二章|二○一三年一月五日|无|小行星225|-0.5| -0.732153 |
| 三个|二○一三年一月六日|无|小行星2254|-0.5|小行星1.939261|
| 四个|二○一三年一月十五日|无|二二五五|-0.5| -0.072585 |
可以看到shop_id列移到了末尾,原因与amiola的回答中解释的相同(即,按照ColumnTrasnformer中的变换顺序对列重新排序)。为了克服这个问题,您可以在转换后将verbose_feature_names_out设置为False以保留相同的起始列名(注意这些名称必须是唯一的,参见文档)。也不需要创建特定的passthrough步骤。

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

| | 日期|日期块编号|商店标识|项目标识|项目_价格|
| - ------|- ------|- ------|- ------|- ------|- ------|
| 无|二○一三年一月二日|无|2.0版|小行星22514| -0.402369 |
| 1个|二○一三年一月三日|无|-0.5|小行星225| -0.732153 |
| 第二章|二○一三年一月五日|无|-0.5|小行星225| -0.732153 |
| 三个|二○一三年一月六日|无|-0.5|小行星2254|小行星1.939261|
| 四个|二○一三年一月十五日|无|-0.5|二二五五| -0.072585 |

相关问题