LabelEncoder для категориальных функций? - PullRequest
2 голосов
/ 15 апреля 2020

Это может быть вопрос новичка, но я видел много людей, использующих LabelEncoder () для замены категориальных переменных на порядковые. Многие люди используют эту функцию, пропуская несколько столбцов за раз, однако у меня есть некоторые сомнения по поводу неправильной ординальности в некоторых моих функциях и того, как это повлияет на мою модель. Вот пример:

Входные данные

import pandas as pd
import numpy as np
from sklearn.preprocessing import LabelEncoder

a = pd.DataFrame(['High','Low','Low','Medium'])
le = LabelEncoder()
le.fit_transform(a)

Выходные данные

array([0, 1, 1, 2], dtype=int64)

Как видите, порядковые значения не отображаются правильно, так как мой LabelEncoder заботится только о порядок в столбце / массиве (это должно быть High = 1, Med = 2, Low = 3 или наоборот). Насколько сильно неправильное отображение может повлиять на модели, и есть ли простой способ, кроме OrdinalEncoder (), чтобы правильно отобразить эти значения?

1 Ответ

7 голосов
/ 15 апреля 2020

Краткий ответ : Использование LabelEncoder для кодирования порядкового номера любого типа функций - плохая идея!


На самом деле это четко указано в документах, где упоминается, что , как следует из его названия , этот метод кодирования нацелен на кодирование метки :

Этот преобразователь следует использовать для кодирования целевых значений, т. Е. y, а не для ввода X.

Как вы правильно отметили в вопрос, отображающий внутреннюю ординальность элемента в неправильном масштабе, будет иметь очень негативное влияние на производительность модели (то есть пропорционально релевантности функции).

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

Если мы закодируем порядковый признак, используя простой LabelEncoder, который может привести к тому, что элемент, скажем, 1 представляет теплый , 2, что, возможно, будет означать горячий , и 0, представляющий кипение . В таком случае в результате получится дерево с чрезмерно большим количеством разбиений и, следовательно, гораздо более высокой сложностью для того, что должно быть проще моделировать.

Вместо этого правильным подходом будет использование OrdinalEncoder и определите соответствующие схемы сопоставления для порядковых элементов.


Хотя на самом деле видение , почему это плохая идея, будет более интуитивным, чем просто слова.

Давайте проиллюстрируем вышеприведенный пример на простом примере, состоящем из двух порядковых элементов, содержащих диапазон с количеством часов, потраченных студентом на подготовку к экзамену, и среднюю оценку всех предыдущих заданий и целевую переменную. с указанием, прошел ли экзамен или нет. Я определил столбцы датафрейма как pd.Categorical:

df = pd.DataFrame(
        {'Hours of dedication': pd.Categorical(
              values =  ['25-30', '20-25', '5-10', '5-10', '40-45', 
                         '0-5', '15-20', '20-25', '30-35', '5-10',
                         '10-15', '45-50', '20-25'],
              categories=['0-5', '5-10', '10-15', '15-20', 
                          '20-25', '25-30','30-35','40-45', '45-50']),

         'Assignments avg grade': pd.Categorical(
             values =  ['B', 'C', 'F', 'C', 'B', 
                        'D', 'C', 'A', 'B', 'B', 
                        'B', 'A', 'D'],
             categories=['F', 'D', 'C', 'B','A']),

         'Result': pd.Categorical(
             values = ['Pass', 'Pass', 'Fail', 'Fail', 'Pass', 
                       'Fail', 'Fail','Pass','Pass', 'Fail', 
                       'Fail', 'Pass', 'Pass'], 
             categories=['Fail', 'Pass'])
        }
    )

Преимущество определения категориального столбца как pandas 'категориального заключается в том, что мы получаем sh порядок среди его категорий. , как уже упоминалось ранее. Это позволяет намного быстрее сортировать на основе установленного порядка, а не лексической сортировки. И его также можно использовать как простой способ получения кодов для различных категорий в соответствии с их порядком.

Таким образом, кадр данных, который мы будем использовать, выглядит следующим образом:

print(df.head())

  Hours_of_dedication   Assignments_avg_grade   Result
0               20-25                       B     Pass
1               20-25                       C     Pass
2                5-10                       F     Fail
3                5-10                       C     Fail
4               40-45                       B     Pass
5                 0-5                       D     Fail
6               15-20                       C     Fail
7               20-25                       A     Pass
8               30-35                       B     Pass
9                5-10                       B     Fail

соответствующие коды категорий можно получить с помощью:

X = df.apply(lambda x: x.cat.codes)
X.head()

   Hours_of_dedication   Assignments_avg_grade   Result
0                    4                       3        1
1                    4                       2        1
2                    1                       0        0
3                    1                       2        0
4                    7                       3        1
5                    0                       1        0
6                    3                       2        0
7                    4                       4        1
8                    6                       3        1
9                    1                       3        0

Теперь давайте подгоним DecisionTreeClassifier и посмотрим, как дерево определило разбиения:

from sklearn import tree

dt = tree.DecisionTreeClassifier()
y = X.pop('Result')
dt.fit(X, y)

Мы можем визуализировать древовидную структуру, используя plot_tree:

t = tree.plot_tree(dt, 
                   feature_names = X.columns,
                   class_names=["Fail", "Pass"],
                   filled = True,
                   label='all',
                   rounded=True)

enter image description here

Это все ?? Ну ... да! Я на самом деле настроил функции таким образом, что существует такая простая и очевидная связь между функцией Часов посвящения и тем, сдан ли экзамен или нет, давая понять, что проблема должна быть очень простой для моделирования.


Теперь давайте попробуем сделать то же самое, напрямую кодируя все функции с помощью схемы кодирования, которую мы могли бы получить, например, с помощью LabelEncoder, поэтому не обращая внимания на фактическую порядковость функций и просто присвоив случайное значение:

df_wrong = df.copy()
df_wrong['Hours_of_dedication'].cat.set_categories(
             ['0-5','40-45', '25-30', '10-15', '5-10', '45-50','15-20', 
              '20-25','30-35'], inplace=True)
df_wrong['Assignments_avg_grade'].cat.set_categories(
             ['A', 'C', 'F', 'D', 'B'], inplace=True)

rcParams['figure.figsize'] = 14,18
X_wrong = df_wrong.drop(['Result'],1).apply(lambda x: x.cat.codes)
y = df_wrong.Result

dt_wrong = tree.DecisionTreeClassifier()
dt_wrong.fit(X_wrong, y)

t = tree.plot_tree(dt_wrong, 
                   feature_names = X_wrong.columns,
                   class_names=["Fail", "Pass"],
                   filled = True,
                   label='all',
                   rounded=True)

enter image description here

Как и ожидалось, древовидная структура равна намного сложнее , чем необходимо для простой задачи, которую мы пытаемся смоделировать. Для того чтобы дерево правильно предсказывало все обучающие выборки, оно расширилось до глубины 4, когда достаточно одного узла.

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

Итак, подведем итог: сохранение ординальности функций после их кодирования имеет решающее значение, в противном случае, как ясно из этого примера, мы потеряем всю их предсказуемую мощность и просто добавим noise к нашей модели.

...