Machine Learning: Как да кодираме категорийни променливи?

Често в практиката се налага да работим с извадка, която съдържа нечислови данни. Много от алгоритмите за машинно обучение като например линейна регресия или метод на опорните вектори (Support Vector Machines) работят само с числови стойности. Това налага обработката на категорийни променливи по подходящ начин, за да може след това да ги използваме при решаване на различни бизнес задачи.

Категорийните променливи са 2 вида:

  • Ординални – има линейна наредба между отделните стойности (например размер на дрехи – S – най-малко, M – средно и L – най-голямо)
  • Номинални – няма степени или градация между отделните стойности (например цвят на очи, кръвна група и т.н.)

В тази статия ще разгледаме някои от често използваните начини, по които може да бъдат кодирани категорийни променливи.

Извадката, използвана за примерите по-надолу в статията, съдържа 1000 обекта и 10 характеристики, в които има данни на кандидати за отпускане на кредит.

Първите 5 реда от данните изглеждат по следния начин:

RiskSexHousingSaving accountsChecking accountPurposeAgeJobCredit amountDuration
0Yesmaleownlittlelittleradio/TV67211696
1Nofemaleownlittlemoderateradio/TV222595148
2Yesmaleownlittlelittleeducation491209612
3Yesmalefreelittlelittlefurniture/equipment452788242
4Nomalefreelittlelittlecar532487024

От таблицата можете да видите, че имаме 6 категорийни и 4 числови променливи.

Цялостния пример можете да изтеглите от тук.

Кои са начините за кодиране на категорийни променливи?

Класовете за кодиране на категорийните променливи се намират в модула preprocessing на библиотеката Scikit-learn. Във фрагментите от код по-надолу за OneHotEncoder ще използваме ohe, за OrdinalEncoder – oe и за LabelEncoder – le.

1) Заместване на стойностите с replace()

Можем ръчно да кодираме отделни категорийни променливи, като първо създадем речник и срещу всяка стойност сложим конкретно число по наша преценка, което да ѝ съответства. След това чрез метода replace() можем да ги заместим.

# Създаване на речник
cat_dict = {'own':1, 'free':2, 'rent':3,
              'radio/TV':1, 'education':2, 'furniture/equipment':3, 'car':4, 'business':5,
              'domestic appliances':6, 'repairs':7, 'vacation/others':8}

# Заместване на стойностите
X_replaced = X[['Housing','Purpose']].replace(cat_dict)

# Извеждане на първите 5 реда
X_replaced.head()

Резултат:

HousingPurpose
011
111
212
323
424

Получените колони вече са числови и от тип int64. Използването на replace е лесен начин да се кодират променливи, но ако те не притежават голямо разнообразие от стойности. Ако например имаме колони с над 100 различни стойности, би било по-подходящо да се кодират по друг, по-удобен начин.

2) Ordinal Encoding

Ordinal encoding е начин за кодиране на категорийни променливи с линейна наредба, при който стойностите на отделните характеристики се заместват с числа от 0 до N-1, където N e броят на уникалните нечислови стойности в съответната променлива.

  • Свойството .cat.codes

Series структурата на библиотеката Pandas притежава свойство наречено .cat.codes. То може да се достъпи за колони, които са от тип category, като съдържа уникални индекси.

# Смяна на типа на данните
X[['Saving accounts','Checking account']] = X[['Saving accounts','Checking account']].astype('category')

# Кодиране на стойностите в колоните
X[['Saving accounts']] = X['Saving accounts'].cat.codes
X[['Checking account']] = X['Checking account'].cat.codes

# Запазване на данните в нов DataFrame
cat_codes_df = X[['Saving accounts','Checking account']]

# Извеждане на първите 5 реда
cat_codes_df.head()

Резултат:

Saving accountsChecking account
000
101
200
300
400

След достъпване на това свойство, колоните вече са числови и от тип int8, а получените резултати са в DataFrame.

  • Използване на OrdinalEncoder

OrdinalEncoder трансформира стойностите по същия начин както .cat.codes. Разликата е в това, че получените резултати са в масив, а не DataFrame.

# Извършване на трансформацията
oe_out = oe.fit_transform(X[['Saving accounts','Checking account']])

# Създаване на нов DataFrame
oe_df = pd.DataFrame(oe_out, columns=['Saving accounts','Checking account'])

# Извеждане на първите 5 реда 
oe_df.head()

Резултат:

Saving accountsChecking account
00.00.0
10.01.0
20.00.0
30.00.0
40.00.0

Получените числови стойности са от тип float64 за разлика от тези при използване на .cat.codes.

3) One Hot Encoding

При One Hot Encoding се създават отделни бинарни колони за всяка уникална нечислова стойност в категорийните променливи. Това показва дали отделният обект притежава или не съответната характеристика.

  • Използване на OneHotEncoder

Прави k на брой характеристики, където k е общият брой уникални стойности в отделната категорийна променлива.

# Извършване на трансформацията
ohe_out = ohe.fit_transform(X[['Sex','Housing']])

Получаваме като резултат разредена матрица (sparse matrix) с кодираните стойности. Това е матрица, на която по-голямата част от елементите са равни на нула. Необходимо е да преминем през допълнителни стъпки, за да добавим данните като колони в DataFrame.

# Запазване на имената на новите колони в отделна променлива
new_cols_ohe = ohe.get_feature_names(['Sex','Housing'])

# Създаване на нов DataFrame
ohe_df = pd.DataFrame(ohe_out.toarray(), columns=new_cols_ohe)

# Извеждане на първите 5 реда 
ohe_df.head()

Резултат:

Sex_femaleSex_maleHousing_freeHousing_ownHousing_rent
00.01.00.01.00.0
11.00.00.01.00.0
20.01.00.01.00.0
30.01.01.00.00.0
40.01.01.00.00.0

Както виждате от таблицата, новосъздадените колони са бинарни, а данните в тях са от тип float64.

  • Прилагане на функцията get_dummies() на библиотеката Pandas

Тази функция действа по същия начин като OneHotEncoder, но за разлика от него get_dummies() премахва оригиналните характеристики и чрез параметъра drop_first изключва първата колона от новосъздадените, за да се избегне проблемът мултиколинеарност (линейна зависимост между отделните характеристики), тъй като линейните модели предполагат, че има независимост между характеристиките.

# Кодиране на стойностите
dummy_df = pd.get_dummies(X[['Sex','Housing']], drop_first=True)

# Извеждане на първите 5 реда 
dummy_df.head()

Резултат:

Sex_maleHousing_ownHousing_rent
0110
1010
2110
3100
4100

Като краен резултат получаваме DataFrame. Колоните са от тип uint8 за разлика от тези, които получихме при OneHotEncoder. Предимство на get_dummies() е че автоматично именува новосъздадените колони с подходящи имена за разлика от OneHotEncoder.

Използването на one hot encoding значително увеличава броя дименсии в извадката, което често е проблем за моделите, тъй като се увеличава обемът на извадката и е по-трудно графично да се представят данните.

4) Кодиране на стойностите на целевата променлива

Когато целевата променлива съдържа нечислови стойности, е необходимо те също да бъдат кодирани. Данните в този случай се трансформират по същия начин както при ordinal encoding.

  • Използване на LabelEncoder

LabelEncoder e част от модула preprocessing на библиотеката Scikit-learn и има същата функционалност като OrdinalEncoder, но за разлика от него, работи единствено със структура, която има само 1 колона.

# Извършване на трансформацията
le_out = le.fit_transform(y)

# Запазване на данните в нов DataFrame
y_enc = pd.DataFrame(data=le_out, columns=[Risk])

# Извеждане на първите 5 реда
y_enc.head()

Резултат:

Risk
01
10
21
31
40

След извършване на трансформацията, за всяка една уникална нечислова стойност в променливата съответства конкретно цяло число.

5) Други начини

Методите за трансформиране на категорийни променливи в числови не се изчерпват до тук. Има още много различни варианти за кодиране на данните. Библиотеката Category Encoders е изградена специално за тази цел. Тя е създадена на базата на Scikit-learn и предлага голямо разнообразие от възможности за кодиране на категорийни данни.

Някои от методите са например:

  • Binary encoding – наподобява one hot encoding, но вместо за всяка уникална стойност да се създава нова колона, при binary encoding тя се превръща в двоично число и всяка негова цифра се отделя в отделна колона.
  • Weight of evidence – използва се, когато целевата променлива е бинарна, а стойностите, които използваме за кодиране на категорийната променлива, се изчисляват чрез формулата:
WoE = ln(\frac{P(1)}{P(0)})
  • P(1) – вероятността на положителния клас
  • P(0) – вероятността на отрицателния клас

Кодиране чрез WoE ни позволява да създадем връзка между категорийната променлива и целевата. Този подход работи най-добре при модели, използващи логистична регресия, тъй като трансформираните данни са на логаритмичната скала.

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

Кой подход да изберем?

Изборът на метод за кодиране на категорийни променливи зависи от това с какви данни разполагаме – дали те имат голямо разнообразие от стойности и от кой тип са – номинални или ординални. На базата на това се взима решение за това кой е подходът, който трябва да се предприеме. Ако променливите са номинални и нямат голям брой уникални стойности, може да се използва one hot encoding, а ако са ординални, да се приложи ordinal encoding. Възможно е и някой от другите методи да даде добър краен резултат. Нужно първо да се направят множество тестове, за да се прецени кой подход е най-подходящ в конкретната задача.

Искате да научите повече за машинното обучение?

Включете се в курса по машинно обучение и анализ на данни с Python.

Научете повече

Автор: Десислава Христова