Краткий ответ : Использование 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)
Это все ?? Ну ... да! Я на самом деле настроил функции таким образом, что существует такая простая и очевидная связь между функцией Часов посвящения и тем, сдан ли экзамен или нет, давая понять, что проблема должна быть очень простой для моделирования.
Теперь давайте попробуем сделать то же самое, напрямую кодируя все функции с помощью схемы кодирования, которую мы могли бы получить, например, с помощью 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)
Как и ожидалось, древовидная структура равна намного сложнее , чем необходимо для простой задачи, которую мы пытаемся смоделировать. Для того чтобы дерево правильно предсказывало все обучающие выборки, оно расширилось до глубины 4
, когда достаточно одного узла.
Это будет означать, что классификатор, вероятно, будет соответствовать, так как мы резко увеличивая сложность. И, обрезая дерево и настраивая необходимые параметры для предотвращения переоснащения, мы также не решаем проблему, поскольку добавили слишком много шума, неправильно кодируя функции.
Итак, подведем итог: сохранение ординальности функций после их кодирования имеет решающее значение, в противном случае, как ясно из этого примера, мы потеряем всю их предсказуемую мощность и просто добавим noise к нашей модели.