【问题标题】:Column-specific processing in an sklearn pipelinesklearn 管道中的列特定处理
【发布时间】:2020-06-02 00:54:23
【问题描述】:

我有一种情况,我需要在管道中进行一些特定于列的处理,但由于转换器返回 numpy 数组而不是 pandas 数据帧,所以我没有列名来进行特征工程。

这是一个简单的、可重复的示例,其中我有一个名为 engineer_feature 的函数,我想用它来创建新数据。我需要在管道期间/之后使用它,因为它取决于一列被估算,并且我希望它能够在 k 折交叉验证期间执行。

import numpy as np
import pandas as pd

from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import FunctionTransformer, OneHotEncoder, StandardScaler

df = pd.DataFrame({"Type": ["Beta", "Beta", "Alpha", "Charlie", "Beta", "Charlie"], "A": [1, 2, 3, np.nan, 22, 4], "B": [5, 7, 12, 21, 12, 10]})

def engineer_feature(df):
    df["C"] = df["A"] / df["B"]
    return df

categorical_transformer = Pipeline([
    ("one_hot", OneHotEncoder())
])

numeric_transformer = Pipeline([
    ("imputer", SimpleImputer()),
    ("engineer", FunctionTransformer(engineer_feature)),
    ("scaler", StandardScaler())
])

preprocessor = ColumnTransformer([
    ("categorical", categorical_transformer, ["Type"]),
    ("numeric", numeric_transformer, ["A", "B"])
])

preprocessor.fit_transform(df)

产生此错误:

IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices

这是有道理的,因为 engineer_feature 试图索引列,就好像它们只是 numpy 数组时的数据帧一样。

解决此问题的策略是什么?我不想硬编码列索引以通过 numpy 访问它们,特别是因为我的真实数据框有更多列。

【问题讨论】:

  • 我不确定我是否理解。查看文档,似乎我传递给OneHotEncoder() 与给出的示例没有任何不同。你能具体说明我做错了什么吗?无论如何,在我的示例中,OneHotEncoder() 可以正常工作并按照我期望的方式转换我的数据。 Here是我指的官方例子。

标签: python pandas numpy scikit-learn


【解决方案1】:

有一些方法可以通过添加几个步骤并简化整个方法来解决您的挑战,而不是尝试在单个输入数据帧上运行所有内容。

  • 对于一种热编码,您可以使用pandas 中的get_dummies() 函数。
  • 为了计算df["C"],您可以编写一个lambda 函数,并使用pandas 中的apply 函数将其应用于数据帧中的所有行。
  • 您仍应依靠sklearn 来估算和缩放数字列。
  • 正如您正确提到的,sklearn 的输出将是一个numpy 数组。您应该将其转换回可进一步使用的 pandas 数据帧。

为了遵循上述方法,

  • 将您的数据框分成两部分,一个包含分类列,另一个包含数字。完成数据处理后,使用 pandas 中的 append 将它们附加回来。

    df_numeric.append(df_catgeorical)
    
  • 您需要将每个步骤的输出保存在新的数据帧中,并将其进一步传递到数据管道的下游。

  • 要释放内存占用,请删除旧数据帧并调用垃圾收集器

    import gc
    
    del df
    gc.collect() 
    
  • 您不需要保存numpy 数组的列索引。只需使用 df.columns 将数据框的列作为列表返回。例如,以下是将sklearn 转换的输出转换为数据帧的方法

    sim = SimpleImputer()
    sklearn_output_array = sim.fit_transform(df_input)
    
    df_output = pd.DataFrame(sklearn_output_array, columns=df_input.columns)
    
    del df_input
    del sklearn_output_array
    gc.collect()
    
    df_output["C"] = df_output["A"] / df_output["B"]
    

我同意上述方法会增加代码行数。但是,我们的代码将更具可读性和更容易理解。

除了上述内容之外,下面是另一个堆栈溢出帖子,它处理单热编码和保存转换后的数据帧的列名以供下游进一步使用。答案包含一些您可能会觉得有用的代码示例。

https://stackoverflow.com/a/60107683/12855052

希望这对您有所帮助,如果您还有其他问题,请告诉我!

【讨论】:

  • 您好,尼克,感谢您的回答。我对这种方法的唯一担忧是它是在Pipeline 上下文之外完成的,因此当我稍后使用GridSearchCV 进行k-fold 交叉验证时,我会遇到一些数据泄漏;这就是导致我遇到问题的原因。
【解决方案2】:

感谢 Nick 和 Sergey 的讨论和回答(特别是我确实知道我将数据框的哪些列传递给 engineer_feature),我想出了一个解决方案这是我可以接受的;不过如果有人有更好的想法,请加入。

import numpy as np
import pandas as pd

from functools import partial
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import FunctionTransformer, OneHotEncoder, StandardScaler

df = pd.DataFrame({"Type": ["Beta", "Beta", "Alpha", "Charlie", "Beta", "Charlie"], "A": [1, 2, 3, np.nan, 22, 4], "B": [5, 7, 12, 21, 12, 10]})

def engineer_feature(columns, X):
    df = pd.DataFrame(X, columns=columns)
    df["C"] = df["A"] / df["B"]
    return df

categorical_transformer = Pipeline([
    ("one_hot", OneHotEncoder())
])

def numeric_transformer(columns):
    transformer = Pipeline([
        ("imputer", SimpleImputer()),
        ("engineer", FunctionTransformer(partial(engineer_feature, columns))),
        ("scaler", StandardScaler())
    ])

    return ("numeric", transformer, columns)

preprocessor = ColumnTransformer([
    ("categorical", categorical_transformer, ["Type"]),
    numeric_transformer(["A", "B"])
])

preprocessor.fit_transform(df)

这取决于AB 两列至少有一个值,这样SimpleImputer 就不会删除该列。

【讨论】:

    【解决方案3】:

    要使您的玩具示例正常工作,您需要:

    def engineer_feature(X):
        return np.c_[X,X[:,0]/X[:,1]]
    
    categorical_transformer = Pipeline([
        ("one_hot", OneHotEncoder())
    ])
    
    numeric_transformer = Pipeline([
        ("imputer", SimpleImputer())
        ,("engineer", FunctionTransformer(engineer_feature))
        ,("scaler", StandardScaler())
    ])
    
    preprocessor = ColumnTransformer([
        ("categorical", categorical_transformer, ["Type"]),
        ("numeric", numeric_transformer, ["A", "B"])
    ])
    
    preprocessor.fit_transform(df)
    

    FunctionTransformer()acceptsnumpy 数组,这里不能避免硬编码。

    【讨论】:

    • 感谢您的回答。但是,FunctionTransformer 可以在ColumnTransformer 的上下文中获取数据帧,here 是一个简单的示例,说明了如何。也许这有助于说明我正在处理的问题。
    猜你喜欢
    • 2020-11-30
    • 2021-04-09
    • 2021-12-18
    • 1970-01-01
    • 2021-11-01
    • 2017-12-23
    • 2012-01-17
    • 2020-08-04
    • 2016-07-06
    相关资源
    最近更新 更多