将 ColumnTransformer() 结果附加到管道中的原始数据?

Posted

技术标签:

【中文标题】将 ColumnTransformer() 结果附加到管道中的原始数据?【英文标题】:Appending the ColumnTransformer() result to the original data within a pipeline? 【发布时间】:2019-07-02 16:16:34 【问题描述】:

这是我的输入数据:

这是对 r、f 和 m 列应用转换的所需输出,结果将附加到原始数据中

代码如下:

import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import PowerTransformer    

df = pd.DataFrame(np.random.randint(0,100,size=(10, 3)), columns=list('rfm'))
column_trans = ColumnTransformer(
    [('r_std', StandardScaler(), ['r']),
     ('f_std', StandardScaler(), ['f']),
     ('m_std', StandardScaler(), ['m']),
     ('r_boxcox', PowerTransformer(method='box-cox'), ['r']),
     ('f_boxcox', PowerTransformer(method='box-cox'), ['f']),
     ('m_boxcox', PowerTransformer(method='box-cox'), ['m']),
    ])

transformed = column_trans.fit_transform(df)
new_cols = ['r_std', 'f_std', 'm_std', 'r_boxcox', 'f_boxcox', 'm_boxcox']

transformed_df = pd.DataFrame(transformed, columns=new_cols)
pd.concat([df, transformed_df], axis = 1)

我还需要额外的转换器,因此我需要将原始列保留在管道中。有没有更好的方法来处理这个?特别是在管道中进行连接和列命名?

【问题讨论】:

【参考方案1】:

一种方法是使用一个虚拟转换器,它只返回转换后的列及其原始值:

import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import PowerTransformer    

np.random.seed(1714)

class NoTransformer(BaseEstimator, TransformerMixin):
    def fit(self, X, y=None):
        return self

    def transform(self, X):
        assert isinstance(X, pd.DataFrame)
        return X

我正在向数据集添加一个 id 列,这样我就可以在 ColumnTransformer() 中显示 remainder 参数的使用,我觉得这非常有用。

df = pd.DataFrame(np.hstack((np.arange(10).reshape((10, 1)),
                             np.random.randint(1,100,size=(10, 3)))),
                  columns=["id"] + list('rfm'))

使用 remainder 和值 passthrough(默认值为 drop)可以保留未转换的列;来自docs。

并且使用 NoTransformer() 虚拟类,我们可以将列 'r'、'f'、'm' 转换为具有相同的值。

column_trans = ColumnTransformer(
    [('r_original', NoTransformer(), ['r']),
     ('f_original', NoTransformer(), ['f']),
     ('m_original', NoTransformer(), ['m']),
     ('r_std', StandardScaler(), ['r']),
     ('f_std', StandardScaler(), ['f']),
     ('m_std', StandardScaler(), ['m']),
     ('r_boxcox', PowerTransformer(method='box-cox'), ['r']),
     ('f_boxcox', PowerTransformer(method='box-cox'), ['f']),
     ('m_boxcox', PowerTransformer(method='box-cox'), ['m']),
    ], remainder="passthrough")

如果您想转换更多列,提示:适合的 ColumnTransformer() 类(在您的情况下为 column_trans)有一个 transformers_ 方法可以让您访问名称['r_std', 'f_std', 'm_std', 'r_boxcox', 'f_boxcox', 'm_boxcox'] 以编程方式:

column_trans.transformers_

#[('r_original', NoTransformer(), ['r']),
# ('f_original', NoTransformer(), ['f']),
# ('m_original', NoTransformer(), ['m']),
# ('r_std', StandardScaler(copy=True, with_mean=True, with_std=True), ['r']),
# ('f_std', StandardScaler(copy=True, with_mean=True, with_std=True), ['f']),
# ('m_std', StandardScaler(copy=True, with_mean=True, with_std=True), ['m']),
# ('r_boxcox',
#  PowerTransformer(copy=True, method='box-cox', standardize=True),
#  ['r']),
# ('f_boxcox',
#  PowerTransformer(copy=True, method='box-cox', standardize=True),
#  ['f']),
# ('m_boxcox',
#  PowerTransformer(copy=True, method='box-cox', standardize=True),
#  ['m']),
# ('remainder', 'passthrough', [0])]


最后,我认为你的代码可以这样简化:

column_trans_2 = ColumnTransformer(
    ([
     ('original', NoTransformer(), ['r', 'f', 'm']),
     ('std', StandardScaler(), ['r', 'f', 'm']),
     ('boxcox', PowerTransformer(method='box-cox'), ['r', 'f', 'm']),
    ]), remainder="passthrough")

transformed_2 = column_trans_2.fit_transform(df)
column_trans_2.transformers_

#[('std',
#  StandardScaler(copy=True, with_mean=True, with_std=True),
#  ['r', 'f', 'm']),
# ('boxcox',
#  PowerTransformer(copy=True, method='box-cox', standardize=True),
#  ['r', 'f', 'm'])]

并通过transformers_以编程方式分配列名:

new_col_names = []
for i in range(len(column_trans_2.transformers)):
    new_col_names += [column_trans_2.transformers[i][0] + '_' + s for s in column_trans_2.transformers[i][2]]
# The non-transformed columns ('id' in this case) will be appended on the right of
# the array and do not show up in the 'transformers_' method.
# Add the id columns to the col_names manually
new_col_names += ['id']

# ['original_r', 'original_f', 'original_m', 'std_r', 'std_f', 'std_m', 'boxcox_r',
#  'boxcox_f', 'boxcox_m', 'id']


pd.DataFrame(transformed_2, columns=new_col_names)

【讨论】:

【参考方案2】:

是的,有一种方法可以做到这一点,幸运的是,它包含在 SKLearn 中。在ColumnTransformer 的原始文档中,您可以找到一个令人困惑但有用的行,如下:

transformer‘drop’, ‘passthrough’ or estimator

Estimator 必须支持 fit 和 transform。特殊情况的字符串“drop”和“passthrough”也被接受,分别表示删除列或将它们通过未转换的方式。

这意味着如果您想在 ColumnTransformer 期间保留一列或在 ColumnTransformer 期间删除一列,您可以简单地使用两个特殊大小写字符串之一来指示它,就像这样:

column_trans = ColumnTransformer(
[('r_std', StandardScaler(), ['r']),
 ('f_std', StandardScaler(), ['f']),
 ('m_std', StandardScaler(), ['m']),
 ('r_boxcox', PowerTransformer(method='box-cox'), ['r']),
 ('f_boxcox', PowerTransformer(method='box-cox'), ['f']),
 ('m_boxcox', PowerTransformer(method='box-cox'), ['m']),
 ('col_keep', 'passthrough', ['r','f','m'])
])

如果您随后使用ColumnTransformer,这 3 列将被保留而不被删除。或者,如果您使用'drop' 而不是'passthrough',您可以选择性地删除某些列。这与remainder='passthrough' 结合使用将允许您删除一些列并保留所有其他列。我希望你觉得这很有用!

【讨论】:

以上是关于将 ColumnTransformer() 结果附加到管道中的原始数据?的主要内容,如果未能解决你的问题,请参考以下文章

管道内的 ColumnTransformer

将 FunctionTransformer 与 sklearn Pipeline 和 ColumnTransformer 一起使用 - 错误:无效的类型提升

sklearn OneHotEncoder 与 ColumnTransformer 导致稀疏矩阵代替创建假人

为啥 ColumnTransformer 中的 SimpleImputer 会创建额外的列?

用于相交列列表的一致 ColumnTransformer

带有 ColumnTransformer 的 SKLearn 管道:'numpy.ndarray' 对象没有属性'lower'