Часто наборы данных содержат категориальные переменные.
дополнительно см. статью Использование типа данных категории в pandas.
Эти переменные обычно хранятся в виде текстовых значений, которые представляют различные характеристики. Некоторые примеры включают цвет ("Красный", "Желтый", "Синий"), размер ("Маленький", "Средний", "Большой") или географические обозначения ("Штат" или "Страна").
Многие алгоритмы машинного обучения поддерживают категориальные значения без дополнительных манипуляций, но есть множество алгоритмов, которые этого не делают. Следовательно, перед аналитиком стоит задача выяснить, как преобразовать эти текстовые атрибуты в числовые значения для дальнейшей обработки.
Оригинал статьи Криса по ссылке
Как и во многих других аспектах здесь нет однозначного ответа. Каждый подход имеет свои плюсы/минусы и может повлиять на результат анализа. К счастью, инструменты Python, такие как pandas и scikit-learn, предоставляют несколько методик. Эта статья является обзором популярных (и более сложных) подходов в надежде, что это поможет другим применить рассмотренные методы к своим задачам.
Для этой статьи мне удалось найти хороший набор данных в репозитории машинного обучения UCI. Этот автомобильный набор данных включает хорошее сочетание категориальных, а также непрерывных значений и служит полезным примером. Поскольку понимание предметной области является важным аспектом при принятии решения о том, как кодировать различные категориальные значения, этот набор данных является хорошим примером.
Прежде чем мы начнем кодировать значения, нам нужно произвести небольшую очистку.
К счастью, в pandas это делается просто:
import pandas as pd
import numpy as np
# Определите заголовки, так как данные их не содержат
headers = ["symboling", "normalized_losses", "make", "fuel_type", "aspiration",
"num_doors", "body_style", "drive_wheels", "engine_location",
"wheel_base", "length", "width", "height", "curb_weight",
"engine_type", "num_cylinders", "engine_size", "fuel_system",
"bore", "stroke", "compression_ratio", "horsepower", "peak_rpm",
"city_mpg", "highway_mpg", "price"]
# Прочтите CSV-файл и преобразуйте "?" в NaN
df = pd.read_csv("https://github.com/dm-fedorov/pandas_basic/blob/master/%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5%20%D0%B2%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B2%20pandas/data/imports-85.data?raw=True",
header=None, names=headers, na_values="?" )
df.head()
symboling | normalized_losses | make | fuel_type | aspiration | num_doors | body_style | drive_wheels | engine_location | wheel_base | ... | engine_size | fuel_system | bore | stroke | compression_ratio | horsepower | peak_rpm | city_mpg | highway_mpg | price | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 3 | NaN | alfa-romero | gas | std | two | convertible | rwd | front | 88.6 | ... | 130 | mpfi | 3.47 | 2.68 | 9.0 | 111.0 | 5000.0 | 21 | 27 | 13495.0 |
1 | 3 | NaN | alfa-romero | gas | std | two | convertible | rwd | front | 88.6 | ... | 130 | mpfi | 3.47 | 2.68 | 9.0 | 111.0 | 5000.0 | 21 | 27 | 16500.0 |
2 | 1 | NaN | alfa-romero | gas | std | two | hatchback | rwd | front | 94.5 | ... | 152 | mpfi | 2.68 | 3.47 | 9.0 | 154.0 | 5000.0 | 19 | 26 | 16500.0 |
3 | 2 | 164.0 | audi | gas | std | four | sedan | fwd | front | 99.8 | ... | 109 | mpfi | 3.19 | 3.40 | 10.0 | 102.0 | 5500.0 | 24 | 30 | 13950.0 |
4 | 2 | 164.0 | audi | gas | std | four | sedan | 4wd | front | 99.4 | ... | 136 | mpfi | 3.19 | 3.40 | 8.0 | 115.0 | 5500.0 | 18 | 22 | 17450.0 |
5 rows × 26 columns
Посмотрим, какие типы данных у нас есть:
df.dtypes
symboling int64 normalized_losses float64 make object fuel_type object aspiration object num_doors object body_style object drive_wheels object engine_location object wheel_base float64 length float64 width float64 height float64 curb_weight int64 engine_type object num_cylinders object engine_size int64 fuel_system object bore float64 stroke float64 compression_ratio float64 horsepower float64 peak_rpm float64 city_mpg int64 highway_mpg int64 price float64 dtype: object
Поскольку в этой статье мы сосредоточимся только на кодировании категориальных переменных, мы собираемся включить в наш фрейм данных только столбцы типа object
.
дополнительно см. статью Обзор типов данных Pandas)
В pandas есть полезная функция select_dtypes
, которую можно использовать для создания нового фрейма данных, содержащего только столбцы типа object
:
obj_df = df.select_dtypes(include=['object']).copy()
obj_df.head()
make | fuel_type | aspiration | num_doors | body_style | drive_wheels | engine_location | engine_type | num_cylinders | fuel_system | |
---|---|---|---|---|---|---|---|---|---|---|
0 | alfa-romero | gas | std | two | convertible | rwd | front | dohc | four | mpfi |
1 | alfa-romero | gas | std | two | convertible | rwd | front | dohc | four | mpfi |
2 | alfa-romero | gas | std | two | hatchback | rwd | front | ohcv | six | mpfi |
3 | audi | gas | std | four | sedan | fwd | front | ohc | four | mpfi |
4 | audi | gas | std | four | sedan | 4wd | front | ohc | five | mpfi |
Прежде чем идти дальше, в данных есть пара нулевых значений, которые необходимо очистить:
obj_df[obj_df.isnull().any(axis=1)]
make | fuel_type | aspiration | num_doors | body_style | drive_wheels | engine_location | engine_type | num_cylinders | fuel_system | |
---|---|---|---|---|---|---|---|---|---|---|
27 | dodge | gas | turbo | NaN | sedan | fwd | front | ohc | four | mpfi |
63 | mazda | diesel | std | NaN | sedan | fwd | front | ohc | four | idi |
Для простоты заполните значение числом four
(так как это наиболее распространенное значение):
obj_df["num_doors"].value_counts()
four 114 two 89 Name: num_doors, dtype: int64
obj_df = obj_df.fillna({"num_doors": "four"})
obj_df[obj_df.isnull().any(axis=1)]
make | fuel_type | aspiration | num_doors | body_style | drive_wheels | engine_location | engine_type | num_cylinders | fuel_system |
---|
obj_df.head()
make | fuel_type | aspiration | num_doors | body_style | drive_wheels | engine_location | engine_type | num_cylinders | fuel_system | |
---|---|---|---|---|---|---|---|---|---|---|
0 | alfa-romero | gas | std | two | convertible | rwd | front | dohc | four | mpfi |
1 | alfa-romero | gas | std | two | convertible | rwd | front | dohc | four | mpfi |
2 | alfa-romero | gas | std | two | hatchback | rwd | front | ohcv | six | mpfi |
3 | audi | gas | std | four | sedan | fwd | front | ohc | four | mpfi |
4 | audi | gas | std | four | sedan | 4wd | front | ohc | five | mpfi |
Теперь, когда данные не имеют нулевых значений, можем рассмотреть варианты кодирования категориальных значений.
Прежде чем мы перейдем к более "стандартным" подходам кодирования категориальных данных, этот набор данных включает один потенциальный подход, который я называю "найти и заменить".
Есть два столбца данных, значения которых представляют собой слова, используемые для представления чисел. В частности, количество цилиндров в двигателе (num_cylinders
) и количество дверей в машине (num_doors
). Pandas позволяет нам напрямую заменять текстовые значения их числовыми эквивалентами, используя replace
.
Мы уже видели, что данные num_doors
включают только 2
или 4
двери. Количество цилиндров включает всего 7
значений, которые легко переводятся в действительные числа:
obj_df["num_cylinders"].value_counts()
four 159 six 24 five 11 eight 5 two 4 twelve 1 three 1 Name: num_cylinders, dtype: int64
Если вы просмотрите документацию по replace
, то увидите, что это мощная функция с множеством параметров.
Для наших целей мы создадим словарь сопоставления (mapping), содержащий столбец для обработки (ключ словаря), а также словарь значений для замены.
Вот полный словарь для очистки столбцов num_doors
и num_cylinders
:
cleanup_nums = {"num_doors": {"four": 4, "two": 2},
"num_cylinders": {"four": 4, "six": 6, "five": 5, "eight": 8,
"two": 2, "twelve": 12, "three":3 }}
Чтобы преобразовать столбцы в числа с помощью replace
:
obj_df = obj_df.replace(cleanup_nums)
obj_df.head()
make | fuel_type | aspiration | num_doors | body_style | drive_wheels | engine_location | engine_type | num_cylinders | fuel_system | |
---|---|---|---|---|---|---|---|---|---|---|
0 | alfa-romero | gas | std | 2 | convertible | rwd | front | dohc | 4 | mpfi |
1 | alfa-romero | gas | std | 2 | convertible | rwd | front | dohc | 4 | mpfi |
2 | alfa-romero | gas | std | 2 | hatchback | rwd | front | ohcv | 6 | mpfi |
3 | audi | gas | std | 4 | sedan | fwd | front | ohc | 4 | mpfi |
4 | audi | gas | std | 4 | sedan | 4wd | front | ohc | 5 | mpfi |
Хорошим преимуществом этого подхода является то, что pandas "знает" типы значений в столбцах, поэтому теперь объект имеет тип int64
:
obj_df.dtypes
make object fuel_type object aspiration object num_doors int64 body_style object drive_wheels object engine_location object engine_type object num_cylinders int64 fuel_system object dtype: object
Хотя данный подход может работать только в определенных случаях, это очень полезная демонстрация того, как преобразовать текстовые значения в числовые, когда есть "легкая" интерпретация данных человеком. Представленная концепция также полезна для более общей очистки данных.
Другой подход к кодированию категориальных значений заключается в использовании метода, называемого кодированием меток (label encoding
).
Кодирование метки - это простое преобразование каждого значения в столбце в число. Например, столбец body_style
содержит 5
разных значений. Мы могли бы закодировать это так:
кабриолет (convertible) -> 0
"жесткий верх" (hardtop) -> 1
хэтчбек (hatchback) -> 2
седан (sedan) -> 3
"вэгон" (wagon) -> 4
Прием, который вы можете использовать в pandas, - это преобразовать столбец в категорию, а затем использовать эти значения категории для кодирования метки:
obj_df["body_style"].value_counts()
sedan 96 hatchback 70 wagon 25 hardtop 8 convertible 6 Name: body_style, dtype: int64
obj_df["body_style"] = obj_df["body_style"].astype('category')
obj_df.dtypes
make object fuel_type object aspiration object num_doors int64 body_style category drive_wheels object engine_location object engine_type object num_cylinders int64 fuel_system object dtype: object
Затем вы можете назначить закодированную переменную новому столбцу с помощью метода доступа (accessor) cat.codes
:
obj_df["body_style_cat"] = obj_df["body_style"].cat.codes
obj_df.head()
make | fuel_type | aspiration | num_doors | body_style | drive_wheels | engine_location | engine_type | num_cylinders | fuel_system | body_style_cat | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | alfa-romero | gas | std | 2 | convertible | rwd | front | dohc | 4 | mpfi | 0 |
1 | alfa-romero | gas | std | 2 | convertible | rwd | front | dohc | 4 | mpfi | 0 |
2 | alfa-romero | gas | std | 2 | hatchback | rwd | front | ohcv | 6 | mpfi | 2 |
3 | audi | gas | std | 4 | sedan | fwd | front | ohc | 4 | mpfi | 3 |
4 | audi | gas | std | 4 | sedan | 4wd | front | ohc | 5 | mpfi | 3 |
obj_df.dtypes
make object fuel_type object aspiration object num_doors int64 body_style category drive_wheels object engine_location object engine_type object num_cylinders int64 fuel_system object body_style_cat int8 dtype: object
Приятным аспектом этого подхода является то, что вы получаете преимущества категорий pandas (компактный размер данных, возможность упорядочивания, поддержка построения графиков), но их можно легко преобразовать в числовые значения для дальнейшего анализа.
Кодирование меток имеет тот недостаток, что числовые значения могут быть "неверно интерпретированы" алгоритмами. Например, значение 0
очевидно меньше значения 4
, но действительно ли это соответствует набору данных в реальной жизни? Имеет ли универсал в 4
раза больший вес, чем у кабриолета? В этом примере я так не думаю.
Общий альтернативный подход называется унитарным кодированием (One Hot Encoding). Основная стратегия состоит в том, чтобы преобразовать значение каждой категории в новый столбец и присвоить столбцу значение 1
или 0
(Истина / Ложь). Это дает преимущество в том, что значение не взвешивается неправильно, но имеет обратную сторону добавления дополнительных столбцов в набор данных.
Pandas поддерживает эту возможность с помощью get_dummies
. Эта функция названа так, потому что она создает фиктивные (dummy) / индикаторные переменные (1
или 0
).
Надеюсь, простой пример прояснит это. Мы можем посмотреть на столбец drive_wheels
, где у нас есть значения 4wd
, fwd
или rwd
.
Используя get_dummies
, мы можем преобразовать их в три столбца с 1
или 0
, соответствующими правильному значению:
pd.get_dummies(obj_df, columns=["drive_wheels"]).head()
make | fuel_type | aspiration | num_doors | body_style | engine_location | engine_type | num_cylinders | fuel_system | body_style_cat | drive_wheels_4wd | drive_wheels_fwd | drive_wheels_rwd | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | alfa-romero | gas | std | 2 | convertible | front | dohc | 4 | mpfi | 0 | 0 | 0 | 1 |
1 | alfa-romero | gas | std | 2 | convertible | front | dohc | 4 | mpfi | 0 | 0 | 0 | 1 |
2 | alfa-romero | gas | std | 2 | hatchback | front | ohcv | 6 | mpfi | 2 | 0 | 0 | 1 |
3 | audi | gas | std | 4 | sedan | front | ohc | 4 | mpfi | 3 | 0 | 1 | 0 |
4 | audi | gas | std | 4 | sedan | front | ohc | 5 | mpfi | 3 | 1 | 0 | 0 |
Новый набор данных содержит три новых столбца:
drive_wheels_4wd
drive_wheels_rwd
drive_wheels_fwd
Эта мощная функция, потому что вы можете передать столько столбцов категорий, сколько захотите, и выбрать, как обозначить столбцы с помощью префикса.
Правильное присвоение имен немного упростит дальнейший анализ:
pd.get_dummies(obj_df, columns=["body_style", "drive_wheels"], prefix=["body", "drive"]).head()
make | fuel_type | aspiration | num_doors | engine_location | engine_type | num_cylinders | fuel_system | body_style_cat | body_convertible | body_hardtop | body_hatchback | body_sedan | body_wagon | drive_4wd | drive_fwd | drive_rwd | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | alfa-romero | gas | std | 2 | front | dohc | 4 | mpfi | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
1 | alfa-romero | gas | std | 2 | front | dohc | 4 | mpfi | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
2 | alfa-romero | gas | std | 2 | front | ohcv | 6 | mpfi | 2 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
3 | audi | gas | std | 4 | front | ohc | 4 | mpfi | 3 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
4 | audi | gas | std | 4 | front | ohc | 5 | mpfi | 3 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
Другая концепция, о которой следует помнить, заключается в том, что get_dummies
возвращает полный фрейм данных (dataframe), поэтому вам нужно будет отфильтровать объекты с помощью select_dtypes
перед проведением итогового анализа.
Унитарное кодирование очень полезно, но оно может увеличить количество столбцов, если у вас много уникальных значений в столбце.
В зависимости от набора данных вы можете использовать некоторую комбинацию кодирования меток и унитарного кодирования для создания двоичного столбца, который соответствует вашим потребностям.
В этом конкретном наборе данных есть столбец с именем engine_type
, который содержит несколько разных значений:
obj_df["engine_type"].value_counts()
ohc 148 ohcf 15 ohcv 13 dohc 12 l 12 rotor 4 dohcv 1 Name: engine_type, dtype: int64
Возможно, нас волнует, оснащен ли двигатель верхним распредвалом (Overhead Cam, OHC) или нет.
Другими словами, разные версии OHC одинаковы для этого анализа. Если это так, то мы могли бы использовать метод доступа (accessor) str
и np.where
(функциональная замена условия) для создания нового столбца, который указывает, есть ли в автомобиле двигатель OHC:
obj_df["OHC_Code"] = np.where(obj_df["engine_type"].str.contains("ohc"), 1, 0)
Это удобная функция, но иногда я забываю о синтаксисе, поэтому вот график, показывающий, что мы делаем:
Результирующий фрейм данных выглядит следующим образом (показывает только подмножество столбцов):
obj_df[["make", "engine_type", "OHC_Code"]].head()
make | engine_type | OHC_Code | |
---|---|---|---|
0 | alfa-romero | dohc | 1 |
1 | alfa-romero | dohc | 1 |
2 | alfa-romero | ohcv | 1 |
3 | audi | ohc | 1 |
4 | audi | ohc | 1 |
Это подход подчеркивает, насколько важно знание предметной области для эффективного решения проблемы.
В дополнение к подходу pandas, scikit-learn предоставляет аналогичную функциональность. Лично я считаю, что использование pandas проще для понимания, но подход scikit является оптимальным, когда вы пытаетесь построить прогнозную модель.
Например, если мы хотим выполнить кодирование меток для марки автомобиля (make), нам нужно создать экземпляр объекта OrdinalEncoder
и произвести fit_transform
данных:
from sklearn.preprocessing import OrdinalEncoder
ord_enc = OrdinalEncoder()
obj_df["make_code"] = ord_enc.fit_transform(obj_df[["make"]])
obj_df[["make", "make_code"]].head(11)
make | make_code | |
---|---|---|
0 | alfa-romero | 0.0 |
1 | alfa-romero | 0.0 |
2 | alfa-romero | 0.0 |
3 | audi | 1.0 |
4 | audi | 1.0 |
5 | audi | 1.0 |
6 | audi | 1.0 |
7 | audi | 1.0 |
8 | audi | 1.0 |
9 | audi | 1.0 |
10 | bmw | 2.0 |
scikit-learn также поддерживает двоичное кодирование с помощью OneHotEncoder
.
Мы используем тот же процесс, что и выше, для преобразования данных, но процесс создания фрейма данных (DataFrame) добавляет пару дополнительных шагов.
from sklearn.preprocessing import OneHotEncoder
oe_style = OneHotEncoder()
oe_results = oe_style.fit_transform(obj_df[["body_style"]])
Результатом является массив, который необходимо преобразовать во фрейм данных:
oe_results.toarray()
array([[1., 0., 0., 0., 0.], [1., 0., 0., 0., 0.], [0., 0., 1., 0., 0.], ..., [0., 0., 0., 1., 0.], [0., 0., 0., 1., 0.], [0., 0., 0., 1., 0.]])
pd.DataFrame(oe_results.toarray(), columns=oe_style.categories_).head()
convertible | hardtop | hatchback | sedan | wagon | |
---|---|---|---|---|---|
0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 |
1 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 |
2 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 |
3 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 |
4 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 |
Следующим шагом будет присоединение этих данных обратно к исходному фрейму.
Вот пример:
obj_df = obj_df.join(pd.DataFrame(oe_results.toarray(), columns=oe_style.categories_))
obj_df.head()
make | fuel_type | aspiration | num_doors | body_style | drive_wheels | engine_location | engine_type | num_cylinders | fuel_system | body_style_cat | OHC_Code | make_code | (convertible,) | (hardtop,) | (hatchback,) | (sedan,) | (wagon,) | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | alfa-romero | gas | std | 2 | convertible | rwd | front | dohc | 4 | mpfi | 0 | 1 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 |
1 | alfa-romero | gas | std | 2 | convertible | rwd | front | dohc | 4 | mpfi | 0 | 1 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 |
2 | alfa-romero | gas | std | 2 | hatchback | rwd | front | ohcv | 6 | mpfi | 2 | 1 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 |
3 | audi | gas | std | 4 | sedan | fwd | front | ohc | 4 | mpfi | 3 | 1 | 1.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 |
4 | audi | gas | std | 4 | sedan | 4wd | front | ohc | 5 | mpfi | 3 | 1 | 1.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 |
Ключевым моментом является то, что вам нужно использовать toarray()
для преобразования результатов в формат, который можно преобразовать во фрейм данных.
Есть еще более продвинутые алгоритмы категориального кодирования. У меня нет опыта работы с ними, но, чтобы завершить это руководство, я захотел их включить. В этой статье содержится дополнительная техническая информация.
Другой приятный аспект заключается в том, что автор статьи создал пакет для scikit-learn под названием category_encoders
, который реализует многие из этих подходов. Это очень хороший инструмент, позволяющий взглянуть на проблему с другой точки зрения.
#!pip3 install category_encoders
В первом примере мы попробуем выполнить кодирование обратной разницы (Backward Difference encoding).
Сначала мы получаем чистый фрейм данных и настраиваем BackwardDifferenceEncoder
:
import category_encoders as ce
# Получите новый чистый фрейм данных
obj_df = df.select_dtypes(include=['object']).copy()
# Укажите столбцы для кодирования, затем выполните fit и transform
encoder = ce.BackwardDifferenceEncoder(cols=["engine_type"])
encoder.fit(obj_df, verbose=1)
# https://stackoverflow.com/questions/63589556/getting-is-categorical-is-deprecated-error-while-using-jamessteinencoder
/home/dmf/anaconda3/lib/python3.8/site-packages/category_encoders/utils.py:21: FutureWarning: is_categorical is deprecated and will be removed in a future version. Use is_categorical_dtype instead elif pd.api.types.is_categorical(cols):
BackwardDifferenceEncoder(cols=['engine_type'], mapping=[{'col': 'engine_type', 'mapping': engine_type_0 engine_type_1 engine_type_2 engine_type_3 engine_type_4 \ 1 -0.857143 -0.714286 -0.571429 -0.428571 -0.285714 2 0.142857 -0.714286 -0.571429 -0.428571 -0.285714 3 0.142857 0.285714 -0.571429 -0.428571 -0.285714 4 0.142857 0.285714 0.428571 -0.428571 -0.285714 5 0.142857 0.285714 0.428571 0.571429 -0.285714 6 0.142857 0.285714 0.428571 0.571429 0.714286 7 0.142857 0.285714 0.428571 0.571429 0.714286 -1 0.000000 0.000000 0.000000 0.000000 0.000000 -2 0.000000 0.000000 0.000000 0.000000 0.000000 engine_type_5 1 -0.142857 2 -0.142857 3 -0.142857 4 -0.142857 5 -0.142857 6 -0.142857 7 0.857143 -1 0.000000 -2 0.000000 }])
encoder.fit_transform(obj_df, verbose=1).iloc[:,8:14].head()
/home/dmf/anaconda3/lib/python3.8/site-packages/category_encoders/utils.py:21: FutureWarning: is_categorical is deprecated and will be removed in a future version. Use is_categorical_dtype instead elif pd.api.types.is_categorical(cols):
engine_type_0 | engine_type_1 | engine_type_2 | engine_type_3 | engine_type_4 | engine_type_5 | |
---|---|---|---|---|---|---|
0 | -0.857143 | -0.714286 | -0.571429 | -0.428571 | -0.285714 | -0.142857 |
1 | -0.857143 | -0.714286 | -0.571429 | -0.428571 | -0.285714 | -0.142857 |
2 | 0.142857 | -0.714286 | -0.571429 | -0.428571 | -0.285714 | -0.142857 |
3 | 0.142857 | 0.285714 | -0.571429 | -0.428571 | -0.285714 | -0.142857 |
4 | 0.142857 | 0.285714 | -0.571429 | -0.428571 | -0.285714 | -0.142857 |
Интересно то, что результат не соответствует стандартным единицам и нулям, которые мы видели в предыдущих примерах кодирования.
Если мы попробуем полиномиальное кодирование (polynomial encoding), то получим другое распределение значений, используемых для кодирования столбцов:
encoder = ce.PolynomialEncoder(cols=["engine_type"])
encoder.fit_transform(obj_df, verbose=1).iloc[:,8:14].head()
/home/dmf/anaconda3/lib/python3.8/site-packages/category_encoders/utils.py:21: FutureWarning: is_categorical is deprecated and will be removed in a future version. Use is_categorical_dtype instead elif pd.api.types.is_categorical(cols):
engine_type_0 | engine_type_1 | engine_type_2 | engine_type_3 | engine_type_4 | engine_type_5 | |
---|---|---|---|---|---|---|
0 | -0.566947 | 5.455447e-01 | -0.408248 | 0.241747 | -0.109109 | 0.032898 |
1 | -0.566947 | 5.455447e-01 | -0.408248 | 0.241747 | -0.109109 | 0.032898 |
2 | -0.377964 | -1.110223e-16 | 0.408248 | -0.564076 | 0.436436 | -0.197386 |
3 | -0.188982 | -3.273268e-01 | 0.408248 | 0.080582 | -0.545545 | 0.493464 |
4 | -0.188982 | -3.273268e-01 | 0.408248 | 0.080582 | -0.545545 | 0.493464 |
В этот пакет включено несколько различных алгоритмов, и лучший способ изучить их - попробовать их и посмотреть, поможет ли это повысить точность вашего анализа.
Цель этого раздела показать, как интегрировать особенности функций кодирования scikit-learn в простой конвейер (pipeline) построения модели.
Как упоминалось выше, категориальные кодировщики scikit-learn позволяют включать преобразование в ваши конвейеры, что позволяет упростить процесс построения модели и избежать некоторых ошибок. Я рекомендую это видео в качестве хорошего вступления. Оно послужило основой для изложенного ниже подхода.
Вот очень быстрый пример того, как включить OneHotEncoder
и OrdinalEncoder
в конвейер и использовать cross_val_score
для анализа результатов:
from sklearn.compose import make_column_transformer
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import cross_val_score
# для целей этого анализа используйте только небольшой набор признаков
feature_cols = [
'fuel_type', 'make', 'aspiration', 'highway_mpg',
'city_mpg', 'curb_weight', 'drive_wheels'
]
# Удалите пустые строки с ценами
df_ml = df.dropna(subset=['price'])
X = df_ml[feature_cols]
y = df_ml['price']
Теперь, когда у нас есть данные, давайте создадим преобразователь (transformer) столбцов:
column_trans = make_column_transformer((OneHotEncoder(handle_unknown='ignore'),
['fuel_type', 'make', 'drive_wheels']),
(OrdinalEncoder(), ['aspiration']),
remainder='passthrough')
В этом примере показано, как применять разные типы кодировщиков для определенных столбцов.
Используем аргумент restder='passthrough'
для передачи всех числовых значений через конвейер без каких-либо изменений.
Для модели мы используем простую линейную регрессию, а затем создаем конвейер:
linreg = LinearRegression()
pipe = make_pipeline(column_trans, linreg)
Выполните перекрестную проверку (cross validation) 10
раз, используя отрицательную среднюю абсолютную ошибку (neg_mean_absolute_error
) в качестве функции оценки.
Наконец, возьмите среднее из 10
значений, чтобы увидеть величину ошибки:
cross_val_score(pipe, X, y, cv=10, scoring='neg_mean_absolute_error').mean().round(2)
-2937.16
Очевидно, что здесь можно провести гораздо больше анализа, но это сделано для того, чтобы проиллюстрировать, как использовать функции scikit-learn в более реалистичном конвейере анализа.
Кодирование категориальных переменных - важный шаг в процессе анализа данных. Поскольку существует несколько подходов к кодированию переменных, важно понимать различные варианты и способы их реализации в ваших собственных наборах данных. В экосистеме науки о данных Python есть много полезных подходов к решению этих проблем. Я призываю вас помнить об этих идеях в следующий раз, когда вы обнаружите, что анализируете категориальные переменные.
Подписка на онлайн-обучение