将多个StandardScaler应用于单个组?

时间:2019-04-09 21:58:47

标签: python pandas scikit-learn

是否存在将sklearn的StandardScaler实例链接在一起以独立地按组缩放数据的pythonic方法?也就是说,如果我想独立地找到虹膜数据集的特征;我可以使用以下代码:

from sklearn.datasets import load_iris
data = load_iris()
df = pd.DataFrame(data['data'], columns=data['feature_names'])
df['class'] = data['target']

means = df.groupby('class').mean()
stds = df.groupby('class').std()

df_rescaled = (
    (df.drop(['class'], 1) - means.reindex(df['class']).values) / 
     stds.reindex(df['class']).values)

在这里,我要分别减去平均值和除以每个组的标准偏差。但是,要实现这些手段和stdev有点困难,并且从本质上讲,当我拥有要控制的分类变量时,它会复制StandardScaler的行为。

是否存在更Python / sklearn友好的方式来实现这种缩放?

2 个答案:

答案 0 :(得分:2)

当然,您可以使用任何sklearn操作并将其应用于groupby对象。

首先,提供一些便利包装:

import typing
import pandas as pd

class SklearnWrapper:
    def __init__(self, transform: typing.Callable):
        self.transform = transform

    def __call__(self, df):
        transformed = self.transform.fit_transform(df.values)
        return pd.DataFrame(transformed, columns=df.columns, index=df.index)

这将把您传递给它的任何sklearn变换应用于一个组。

最后是简单的用法:

from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler

data = load_iris()
df = pd.DataFrame(data["data"], columns=data["feature_names"])
df["class"] = data["target"]

df_rescaled = (
    df.groupby("class")
    .apply(SklearnWrapper(StandardScaler()))
    .drop("class", axis="columns")
)

编辑:您几乎可以使用SklearnWrapper做任何事情。 这是对每个组进行转换和反转此操作的示例(例如,不要覆盖转换对象)-每次看到新组时都重新安装该对象(并将其添加到{{1 }})。

我有点复制了list功能,以便于使用(您可以通过将适当的sklearn's传递给string内部方法,将其扩展为所需的任何功能):

_call_with_function

用法(组变换,逆运算并再次应用):

class SklearnWrapper:
    def __init__(self, transformation: typing.Callable):
        self.transformation = transformation
        self._group_transforms = []
        # Start with -1 and for each group up the pointer by one
        self._pointer = -1

    def _call_with_function(self, df: pd.DataFrame, function: str):
        # If pointer >= len we are making a new apply, reset _pointer
        if self._pointer >= len(self._group_transforms):
            self._pointer = -1
        self._pointer += 1
        return pd.DataFrame(
            getattr(self._group_transforms[self._pointer], function)(df.values),
            columns=df.columns,
            index=df.index,
        )

    def fit(self, df):
        self._group_transforms.append(self.transformation.fit(df.values))
        return self

    def transform(self, df):
        return self._call_with_function(df, "transform")

    def fit_transform(self, df):
        self.fit(df)
        return self.transform(df)

    def inverse_transform(self, df):
        return self._call_with_function(df, "inverse_transform")

答案 1 :(得分:0)

我更新了@Szymon Maszke 代码:

class SklearnWrapper:


def __init__(self, transformation: typing.Callable):
    self.transformation = transformation
    self._group_transforms = []
    # Start with -1 and for each group up the pointer by one
    self._pointer = -1

def _call_with_function(self, df: pd.DataFrame, function: str):
    # If pointer >= len we are making a new apply, reset _pointer
    if self._pointer == len(self._group_transforms)-1 and function=="inverse_transform":
        self._pointer = -1
    self._pointer += 1
    print(self._pointer)
    return pd.DataFrame(
        getattr(self._group_transforms[self._pointer], function)(df.values),
        columns=df.columns,
        index=df.index,
    )

def fit(self, df):
    scaler = copy(self.transformation)
    self._group_transforms.append(scaler.fit(df.values))
    return self

def transform(self, df):
    return self._call_with_function(df, "transform")

def fit_transform(self, df):
    self.fit(df)
    return self.transform(df)

def inverse_transform(self, df):
    return self._call_with_function(df, "inverse_transform")

StandardScaler() 未正确存储在 _group_transforms 中,因此我创建了一个副本(使用副本库)并将其存储(也许使用 OOP 有更好的方法)。