如何创建预处理管道,包括内置的 scikit 学习转换器、自定义转换器,其中之一用于特征工程?

How to create a preprocessing pipeline including built-in scikit learn transformers, custom transformers, one of which is for feature engineering?

我正在使用这个数据集:

https://www.kaggle.com/shahir/protein-data-set

总结

我正在努力创建一个带有内置转换器和自定义转换器的预处理管道,其中包括一个可以向数据添加额外属性并进一步对添加的属性执行转换的转换器。

附加属性示例:

这需要输入 phValue 的缺失值,然后创建额外的属性和进一步的转换器,这些转换器也将转换 sequence_length 属性。

我可怕的变形金刚

这是我如何创建自定义转换器的示例,我可以将其用于手动预处理,但是,在创建完整的预处理管道时,这不是处理它的正确方法。

def data_to_frame(X):
    if isinstance(X, pd.DataFrame):
        return X
    elif isinstance(X, sparse.csr_matrix):
        return pd.DataFrame(X, indices, atributes)
    elif isinstance(X, np.ndarray):
        return pd.DataFrame(X, indices, atributes)
    else:
        raise Exception("Incorrect Data Structure Passed")

class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
    def __init__(self, no_difference = True): # no *args or **kargs
        self.no_difference = no_difference
    def fit(self, X, y=None):
        return self # nothing else to do
    def transform(self, X):
        atributes.extend(['sequence_length', 'difference', 'phLabel'])
        sequence_length = X.sequence.str.len()
        difference = X['residueCount'] - sequence_length
        phLabel = X['phValue'].apply(ph_labels)
        if self.no_difference:
            atributes.append('no_difference')
            no_difference = (difference == 0)
            return np.c_[X, sequence_length, difference, phLabel, no_difference]
        else:
            return np.c_[X, sequence_length, difference, phLabel]

Pandas 变形金刚中的操作。

我想在转换器中执行的操作特定于 pandas。我的解决方案是将输入的 numpy 数组转换为数据框,然后 return 将其作为转换函数中的 numpy 数组。我将全局变量用于属性和索引。我意识到这是一种乏善可陈的方法。 我如何在自定义转换器中使用 pandas 操作?

我看到了这个博客 post,但是我无法使用 Column Transformer 做到这一点: https://zablo.net/blog/post/pandas-dataframe-in-scikit-learn-feature-union/

更新:

我的管道的其他问题。 指定要转换的列时,后续转换器如何工作? 它是否将整个集合传递给每个转换器,对指定的列进行操作,并将修改后的完整集合 return 传递给其他转换器? 此外,不为自定义转换器指定列似乎会引发错误,即使它们在我的情况下不起作用,因为我将参数传递给构造函数。我应该如何更改我的代码?

如果我在 fit_transform 之后注释掉 OrdinalEncoder 和 OneHotEncoder,ColumnTransformer 会输出一个形状为 (rows, 72) 的 numpy 数组 有 19 个属性,我在 FeatureSelector 转换器中删除了 2 个属性。所以我希望在没有 OHE 的情况下收到一个 (rows, 17) 数组。

如果我保持原样,我会收到:ValueError: Input contains NaN.

attributes 是我数据集中每一列的全局数组。我删除了我删除的 FeatureSelector 中的值。

# numeric_feat_eng + categ_feat_eng contains all of my attributes
prepoc_pipeline = make_column_transformer(
                  (SimpleImputer(strategy='mean'), numeric_feat_eng),
                  (SimpleImputer(strategy='most_frequent'), categ_feat_eng),
                  (FixAtributeValues(), attributes),
                  (CombinedAttributesAdder(), attributes),
                  (FeatureSelector(attributes_to_drop), attributes_to_drop),
                  (LogTransformation(atr_log_trans), atr_log_trans),
                  (StandardScaler(), numeric_feat_eng),
                  (OrdinalEncoder(), id_cols),
                  (OneHotEncoder(handle_unknown='ignore'), categ_without_ids)
)
class FeatureSelector(BaseEstimator, TransformerMixin):
    def __init__(self, attributes_drop = ['pdbxDetails', 'sequence']):
        self.attributes_drop = attributes_drop
    def fit(self, X, y=None):
        return self # nothing else to do
    def transform(self, X):
        X = data_to_frame(X)
        for x in self.attributes_drop:
            attributes.remove(x)
        X = X.drop(columns=self.attributes_drop)
        return X

如果有人能指导我如何做到这一点,将不胜感激!或者向我提供我可以学习如何创建管道的资源。

这应该按预期工作 - 很可能是您的实现有问题 - 可以尝试使用虚拟数据集。 TransformerMixin 并不真正关心输入是 numpy 还是 pandas.DataFrame,它将按“预期”工作。

import pandas as pd
import numpy as np
from sklearn.base import TransformerMixin
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import FunctionTransformer
from sklearn.pipeline import make_pipeline


class CustomTransformer(TransformerMixin):
    def __init__(self, some_stuff=None, column_names= []):
        self.some_stuff = some_stuff
        self.column_names = column_names
    def fit(self, X, y=None):
        return self

    def transform(self, X):
        # do stuff on X, and return dataframe
        # of the same shape - this gets messy
        # if the preceding item is a numpy array
        # and not a dataframe
        if isinstance(X, np.ndarray):
            X = pd.DataFrame(X, columns=self.column_names)
        
        X['str_len'] = X['my_str'].apply(lambda x: str(x)).str.len()
        X['custom_func'] = X['val'].apply(lambda x: 1 if x > 0.5 else -1)
        return X


df = pd.DataFrame({
    'my_str': [111, 2, 3333],
    'val': [0, 1, 1]
})

# mixing this works as expected
my_pipeline = make_pipeline(StandardScaler(), CustomTransformer(column_names=["my_str", "val"]))
my_pipeline.fit_transform(df)

# using this by itself works as well
my_pipeline = make_pipeline(CustomTransformer(column_names=["my_str", "val"]))
my_pipeline.fit_transform(df)

输出为:

In [  ]: my_pipeline = make_pipeline(StandardScaler(), CustomTransformer(column_names=["my_str", "val"])) 
    ...: my_pipeline.fit_transform(df)                                                                                                                                                                                                  
Out[  ]: 
     my_str       val  str_len  custom_func
0 -0.671543 -1.414214       19           -1
1 -0.742084  0.707107       18            1
2  1.413627  0.707107       17            1

In [  ]: my_pipeline = make_pipeline(CustomTransformer(column_names=["my_str", "val"])) 
    ...: my_pipeline.fit_transform(df)                                                                                                                                                                                                  
Out[  ]: 
   my_str  val  str_len  custom_func
0     111    0        3           -1
1       2    1        1            1
2    3333    1        4            1

或者,如果您想直接将事物映射到数据框,您可以使用 sklearn-pandas

from sklearn_pandas import DataFrameMapper

# using sklearn-pandas
str_transformer = FunctionTransformer(lambda x: x.apply(lambda y: y.str.len()))
cust_transformer = FunctionTransformer(lambda x: (x > 0.5) *2 -1)


mapper = DataFrameMapper([
    (['my_str'], str_transformer),
    (['val'], make_pipeline(StandardScaler(), cust_transformer))
], input_df=True, df_out=True)

mapper.fit_transform(df)

输出:

In [  ]: mapper.fit_transform(df)                                                                                                                                                                                                       
Out[47]: 
   my_str  val
0       3   -1
1       2    1
2       1    1

使用 sklearn pandas 可以让您更具体地将输入作为数据帧,将输出作为数据帧,并允许您将每一列单独映射到每个感兴趣的管道,而不是 encoding/hardcoding 列名称作为 TransformerMixin 对象的一部分。