Применить несколько StandardScaler к отдельным группам?

Есть ли питонический способ связать вместе экземпляры StandardScaler sklearn для независимого масштабирования данных с помощью групп? То есть, если бы я хотел самостоятельно найти масштабные характеристики набора данных радужной оболочки; Я мог бы использовать следующий код:

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 способ реализовать этот тип масштабирования?

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
5
0
3 883
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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 можно делать практически все что угодно. Вот пример преобразование и обращение этой операции для каждой группы (например, не перезаписывать объект преобразования) — просто подгоняйте объект заново каждый раз, когда видна новая группа (и добавляйте ее в list).

Я немного воспроизвел некоторые функциональные возможности sklearn's для упрощения использования (вы можете расширить его любой функцией, которую хотите, передав соответствующий внутренний метод string to _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 для каждой группы.

pstjohn 10.04.2019 04:41

Это то, что вы хотели? Вы можете делать с ним другие вещи (довольно расширяемые) в зависимости от вашей конечной цели.

Szymon Maszke 10.04.2019 12:49

Так что это здорово, большое спасибо за помощь! Я надеялся, что мне сойдет с рук код меньше, но этот определенно более пригоден для повторного использования. Я также просто предупрежу, что это может вызвать проблемы, если группы отсутствовали в тестовом наборе данных (или если были добавлены новые группы) без возникновения ошибки, но я не вижу способа обойти это с помощью синтаксиса groupby/apply.

pstjohn 10.04.2019 15:03

Я думаю, вы могли бы расширить его с помощью filter, реализации partial_fit-подобной функции, но это загромождает изображение. ИМО нет необходимости, если вам это не нужно прямо сейчас (или вы можете создать новую проблему, если вам это нужно).

Szymon Maszke 10.04.2019 15:32

Я использовал это, но если мой столбец класса является категорией, он возвращает ошибку. У вас есть решение для этого случая? @SzymonMaszke

rasyidstat 19.08.2020 08:55

Это дает неверные результаты при попытке инвертировать масштабированный фрейм данных в исходный. @SzymonMaszke

Indrajit 20.01.2021 13:25

@Indrajit В чем именно проблема? По сути, я перекладываю ответственность за результаты на sklearn здесь, но, пожалуйста, поправьте меня, если я ошибаюсь (было бы здорово отредактировать с объяснением, спасибо).

Szymon Maszke 20.01.2021 13:38

Привет @SzymonMaszke, я запускаю тот же пример, что и выше. Когда я генерирую df_rescaled, столбец «класс» содержит значение 0 для всех строк. В результате, когда вы делаете обратное преобразование, результаты неверны.

Indrajit 25.01.2021 14:00

В методе подгонки _group_transforms.append не будет добавлять дубликаты? Что мне здесь не хватает?

Deepak 05.07.2021 17:18

Я обновил код @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, поэтому я создал копию (используя библиотеку копирования) и сохранил ее (возможно, есть лучший способ сделать это с помощью ООП).

Другие вопросы по теме