您能否使用 Sklearn 的 Transformer API 持续跟踪列标签?

Posted

技术标签:

【中文标题】您能否使用 Sklearn 的 Transformer API 持续跟踪列标签?【英文标题】:Can You Consistently Keep Track of Column Labels Using Sklearn's Transformer API? 【发布时间】:2019-12-22 23:56:33 【问题描述】:

对于这个库来说,这似乎是一个非常重要的问题,到目前为止,我还没有看到一个决定性的答案,尽管在大多数情况下,答案似乎是“不”。

现在,任何在sklearn 中使用transformer api 的方法都会返回一个numpy 数组作为其结果。通常这很好,但是如果您将扩展或减少列数的多步骤过程链接在一起,那么没有一种清晰的方式来跟踪它们与原始列标签的关系会导致难以使用本节的这一部分充分利用图书馆。

例如,这是我最近使用的一个 sn-p,其中无法将新列映射到数据集中最初的列是一个很大的缺点:

numeric_columns = train.select_dtypes(include=np.number).columns.tolist()
cat_columns     = train.select_dtypes(include=np.object).columns.tolist()

numeric_pipeline = make_pipeline(SimpleImputer(strategy='median'), StandardScaler())
cat_pipeline     = make_pipeline(SimpleImputer(strategy='most_frequent'), OneHotEncoder())

transformers = [
('num', numeric_pipeline, numeric_columns),
('cat', cat_pipeline, cat_columns)
]

combined_pipe = ColumnTransformer(transformers)

train_clean = combined_pipe.fit_transform(train)

test_clean  = combined_pipe.transform(test)

在此示例中,我使用 ColumnTransformer 拆分了我的数据集,然后使用 OneHotEncoder 添加了其他列,因此我的列排列方式与我开始时的不同。

如果我使用使用相同 API 的不同模块,我可以很容易地做出不同的安排。 OrdinalEncoerselect_k_best

如果您要进行多步转换,有没有办法始终如一地查看新列与原始数据集的关系?

关于它的广泛讨论here,但我认为还没有最终确定。

【问题讨论】:

【参考方案1】:

是的,您是对的,目前尚不完全支持跟踪 sklearn 中的 feature_names。最初,决定在numpy 数组级别将其保持为通用。可以跟踪 sklearn 估计器的功能名称的最新进展here。

无论如何,我们可以创建包装器来获取ColumnTransformer 的特征名称。我不确定它是否可以捕获所有可能的ColumnTransformers 类型。但至少,它可以解决你的问题。

来自Documentation of ColumnTransformer

备注

变换后的特征矩阵中的列顺序遵循变换器列表中列的指定顺序。除非在 passthrough 关键字中指定,否则未指定的原始特征矩阵的列将从生成的转换后的特征矩阵中删除。用 passthrough 指定的那些列被添加到转换器输出的右侧。

试试这个!

import pandas as pd
import numpy as np
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import make_pipeline, Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder, MinMaxScaler
from sklearn.feature_extraction.text import _VectorizerMixin
from sklearn.feature_selection._base import SelectorMixin
from sklearn.feature_selection import SelectKBest
from sklearn.feature_extraction.text import CountVectorizer

train = pd.DataFrame('age': [23,12, 12, np.nan],
                      'Gender': ['M','F', np.nan, 'F'],
                      'income': ['high','low','low','medium'],
                      'sales': [10000, 100020, 110000, 100],
                      'foo' : [1,0,0,1],
                      'text': ['I will test this',
                               'need to write more sentence',
                               'want to keep it simple',
                               'hope you got that these sentences are junk'],
                      'y': [0,1,1,1])
numeric_columns = ['age']
cat_columns     = ['Gender','income']

numeric_pipeline = make_pipeline(SimpleImputer(strategy='median'), StandardScaler())
cat_pipeline     = make_pipeline(SimpleImputer(strategy='most_frequent'), OneHotEncoder())
text_pipeline = make_pipeline(CountVectorizer(), SelectKBest(k=5))

transformers = [
    ('num', numeric_pipeline, numeric_columns),
    ('cat', cat_pipeline, cat_columns),
    ('text', text_pipeline, 'text'),
    ('simple_transformer', MinMaxScaler(), ['sales']),
]

combined_pipe = ColumnTransformer(
    transformers, remainder='passthrough')

transformed_data = combined_pipe.fit_transform(
    train.drop('y',1), train['y'])

def get_feature_out(estimator, feature_in):
    if hasattr(estimator,'get_feature_names'):
        if isinstance(estimator, _VectorizerMixin):
            # handling all vectorizers
            return [f'vec_f' \
                for f in estimator.get_feature_names()]
        else:
            return estimator.get_feature_names(feature_in)
    elif isinstance(estimator, SelectorMixin):
        return np.array(feature_in)[estimator.get_support()]
    else:
        return feature_in


def get_ct_feature_names(ct):
    # handles all estimators, pipelines inside ColumnTransfomer
    # doesn't work when remainder =='passthrough'
    # which requires the input column names.
    output_features = []

    for name, estimator, features in ct.transformers_:
        if name!='remainder':
            if isinstance(estimator, Pipeline):
                current_features = features
                for step in estimator:
                    current_features = get_feature_out(step, current_features)
                features_out = current_features
            else:
                features_out = get_feature_out(estimator, features)
            output_features.extend(features_out)
        elif estimator=='passthrough':
            output_features.extend(ct._feature_names_in[features])
                
    return output_features

pd.DataFrame(transformed_data, 
             columns=get_ct_feature_names(combined_pipe))

【讨论】:

如果新函数不命名为“get_feature_names”会更清楚,因为它与 sklearn 管道的函数命名相同。 (见i.get_feature_names 我认为重要的是要注意,如果您定义了自定义估算器,以及使用此方法的内容,则该估算器必须实现方法get_feature_names 最后一行(pd.DataFrame ....)在我这边产生了一个错误:“DataFrame constructor not proper called”——所以我无法解决问题或使用上面的代码,但是 +1 用于描述管道中功能的顺序是如何工作的。 我不确定你到底有什么问题。我可以在我的机器上运行上述示例。

以上是关于您能否使用 Sklearn 的 Transformer API 持续跟踪列标签?的主要内容,如果未能解决你的问题,请参考以下文章

sklearn standardscaler transform VS fit_transform 输出

sklearn中各算法类的fit,fit_transform和transform函数

sklearn fit transform fit_transform

sklearn.compose.ColumnTransformer:fit_transform() 接受 2 个位置参数,但给出了 3 个

Python初探——sklearn库中数据预处理函数fit_transform()和transform()的区别

为啥 sklearn 预处理 LabelEncoder inverse_transform 只适用于一列?