Обработка столбцов c в конвейере sklearn - PullRequest
1 голос
/ 18 февраля 2020

У меня есть ситуация, когда мне нужно выполнить некоторую обработку столбца c в конвейере, но поскольку преобразователи возвращают массивы numpy, а не pandas фреймы данных, у меня нет имен столбцов для выполнения моей функции engineering.

Вот простой, воспроизводимый пример, где у меня есть функция с именем 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, тем более что в моем реальном кадре данных есть еще много столбцов.

Ответы [ 3 ]

0 голосов
/ 18 февраля 2020

Чтобы ваш игрушечный пример работал, вам необходимо:

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() принимает массив numpy, вы не можете избежать жесткого кодирования здесь.

0 голосов
/ 18 февраля 2020

Благодаря обсуждению и ответам Ника и Сергея (в частности, я знаю знаю, какие столбцы моего информационного кадра я передаю в 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)

Ничего не стоит, это зависит от того, чтобы в обоих столбцах A и B было хотя бы одно значение, чтобы SimpleImputer не опустите столбец.

0 голосов
/ 18 февраля 2020

Есть способы обойти вашу проблему, добавив несколько шагов и упростив весь подход, вместо того, чтобы пытаться выполнить все на одном входном кадре данных.

  • Для одного горячего кодирования вы можете использовать get_dummies() функция в pandas.
  • Для вычисления df["C"] вы можете написать функцию lambda и применить ее ко всем строкам в кадре данных, используя функцию apply в pandas.
  • Вы все равно должны полагаться на sklearn для вменения и масштабирования числовых c столбцов.
  • Как вы правильно заметили, выходные данные sklearn будут массивом numpy. Вы должны преобразовать его обратно в pandas фрейм данных, который можно использовать в дальнейшем.

Чтобы следовать вышеописанному подходу,

  • Разделите ваш фрейм данных на два, один с категориальными столбцами, а другой с цифрой c. Как только вы закончите обработку данных, используйте append в pandas, чтобы добавить их обратно.

    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"]
    

Я согласен с тем, что приведенный выше подход увеличит количество строк код. Тем не менее, наш код будет намного более читабельным и простым для понимания.

В дополнение к вышесказанному ниже приведен еще один пост переполнения стека, в котором рассматривается горячее кодирование и сохранение имен столбцов преобразованных фреймов данных для дальнейшего использования в нисходящем направлении. Ответ содержит несколько примеров с кодом, который может оказаться полезным.

{ ссылка }

Надеюсь, что все это поможет, и дайте мне знать, если у вас есть дополнительные вопросы!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...