【问题标题】:Apply multiple StandardScaler's to individual groups?将多个 StandardScaler 应用于各个组?
【发布时间】:2019-08-31 06:50:41
【问题描述】:

有没有一种 Python 的方法可以将 sklearn 的 StandardScaler 实例链接在一起,以独立地按组缩放数据?即,如果我想找到独立缩放 iris 数据集的特征;我可以使用以下代码:

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)

在这里,我将分别减去均值并除以每个组的标准差。但是当我有一个我想控制的分类变量时,它有点难以实现这些方法和标准开发,并且基本上复制StandardScaler 的行为。

有没有更 Pythonic / sklearn 友好的方式来实现这种缩放?

【问题讨论】:

    标签: python pandas scikit-learn


    【解决方案1】:

    当然,您可以使用任何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 做任何事情。 这是为每个组转换和反转此操作的示例(例如,不要覆盖转换对象) - 每次看到新组时重新调整对象(并将其添加到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")
    

    用法(组变换、逆运算再应用):

    data = load_iris()
    df = pd.DataFrame(data["data"], columns=data["feature_names"])
    df["class"] = data["target"]
    
    # Create scaler outside the class
    scaler = SklearnWrapper(StandardScaler())
    
    # Fit and transform data (holding state)
    df_rescaled = df.groupby("class").apply(scaler.fit_transform)
    
    # Inverse the operation
    df_inverted = df_rescaled.groupby("class").apply(scaler.inverse_transform)
    
    # Apply transformation once again
    df_transformed = (
        df_inverted.groupby("class")
        .apply(scaler.transform)
        .drop("class", axis="columns")
    )
    

    【讨论】:

    • 很酷的解决方案!所以这行得通,但不幸的是你失去了转换新数据的能力。 (或 inverse_transform 旧数据)很抱歉没有将其放在原始问题中。这似乎覆盖了每个组的 SklearnWrapper.transform 类。
    • 这就是你想要的吗?你可以根据你的最终目标用它做其他事情(相当可扩展)。
    • 太好了,非常感谢您的帮助!我希望我能摆脱 less 代码,但这绝对是更可重用的。我还要提醒一下,如果测试数据集中缺少组(或者如果添加了新组)而不引发错误,这可能会导致问题,但我看不到使用 groupby / apply 语法的解决方法。
    • 我认为您可以使用filter 扩展它,实现partial_fit 类似的功能,但它会使图像混乱。如果您现在不需要,IMO 不需要它(或者您可以创建一个新问题)。
    • 我使用了这个,但如果我的类列是类别,它会返回错误。你有这个案例的解决方案吗? @SzymonMaszke
    【解决方案2】:

    我更新了@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 有更好的方法)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-11-20
      • 2012-07-17
      • 2019-08-11
      • 1970-01-01
      • 2021-09-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多