Автор: Десислава Христова
Често в практиката се налага да работим с извадка, която съдържа нечислови данни. Много от алгоритмите за машинно обучение като например линейна регресия или метод на опорните вектори (Support Vector Machines) работят само с числови стойности. Това налага обработката на категорийни променливи по подходящ начин, за да може след това да ги използваме при решаване на различни бизнес задачи.
Категорийните променливи са 2 вида:
В тази статия ще разгледаме някои от често използваните начини, по които може да бъдат кодирани категорийни променливи.
Извадката, използвана за примерите по-надолу в статията, съдържа 1000 обекта и 10 характеристики, в които има данни на кандидати за отпускане на кредит.
Първите 5 реда от данните изглеждат по следния начин:
Risk | Sex | Housing | Saving accounts | Checking account | Purpose | Age | Job | Credit amount | Duration | |
---|---|---|---|---|---|---|---|---|---|---|
0 | Yes | male | own | little | little | radio/TV | 67 | 2 | 1169 | 6 |
1 | No | female | own | little | moderate | radio/TV | 22 | 2 | 5951 | 48 |
2 | Yes | male | own | little | little | education | 49 | 1 | 2096 | 12 |
3 | Yes | male | free | little | little | furniture/equipment | 45 | 2 | 7882 | 42 |
4 | No | male | free | little | little | car | 53 | 2 | 4870 | 24 |
От таблицата можете да видите, че имаме 6 категорийни и 4 числови променливи.
Цялостния пример можете да изтеглите от тук.
Класовете за кодиране на категорийните променливи се намират в модула preprocessing на библиотеката Scikit-learn. Във фрагментите от код по-надолу за OneHotEncoder ще използваме ohe, за OrdinalEncoder - oe и за LabelEncoder - le.
Можем ръчно да кодираме отделни категорийни променливи, като първо създадем речник и срещу всяка стойност сложим конкретно число по наша преценка, което да ѝ съответства. След това чрез метода replace() можем да ги заместим.
1 2 3 4 5 6 7 8 9 10 |
# Създаване на речник 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() |
Резултат:
Housing | Purpose | |
---|---|---|
0 | 1 | 1 |
1 | 1 | 1 |
2 | 1 | 2 |
3 | 2 | 3 |
4 | 2 | 4 |
Получените колони вече са числови и от тип int64. Използването на replace е лесен начин да се кодират променливи, но ако те не притежават голямо разнообразие от стойности. Ако например имаме колони с над 100 различни стойности, би било по-подходящо да се кодират по друг, по-удобен начин.
Ordinal encoding е начин за кодиране на категорийни променливи с линейна наредба, при който стойностите на отделните характеристики се заместват с числа от 0 до N-1, където N e броят на уникалните нечислови стойности в съответната променлива.
Series структурата на библиотеката Pandas притежава свойство наречено .cat.codes. То може да се достъпи за колони, които са от тип category, като съдържа уникални индекси.
1 2 3 4 5 6 7 8 9 10 11 12 |
# Смяна на типа на данните 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 accounts | Checking account | |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
2 | 0 | 0 |
3 | 0 | 0 |
4 | 0 | 0 |
След достъпване на това свойство, колоните вече са числови и от тип int8, а получените резултати са в DataFrame.
OrdinalEncoder трансформира стойностите по същия начин както .cat.codes. Разликата е в това, че получените резултати са в масив, а не DataFrame.
1 2 3 4 5 6 7 8 |
# Извършване на трансформацията 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 accounts | Checking account | |
---|---|---|
0 | 0.0 | 0.0 |
1 | 0.0 | 1.0 |
2 | 0.0 | 0.0 |
3 | 0.0 | 0.0 |
4 | 0.0 | 0.0 |
Получените числови стойности са от тип float64 за разлика от тези при използване на .cat.codes.
При One Hot Encoding се създават отделни бинарни колони за всяка уникална нечислова стойност в категорийните променливи. Това показва дали отделният обект притежава или не съответната характеристика.
Прави k на брой характеристики, където k е общият брой уникални стойности в отделната категорийна променлива.
1 2 |
# Извършване на трансформацията ohe_out = ohe.fit_transform(X[['Sex','Housing']]) |
Получаваме като резултат разредена матрица (sparse matrix) с кодираните стойности. Това е матрица, на която по-голямата част от елементите са равни на нула. Необходимо е да преминем през допълнителни стъпки, за да добавим данните като колони в DataFrame.
1 2 3 4 5 6 7 8 |
# Запазване на имената на новите колони в отделна променлива 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_female | Sex_male | Housing_free | Housing_own | Housing_rent | |
---|---|---|---|---|---|
0 | 0.0 | 1.0 | 0.0 | 1.0 | 0.0 |
1 | 1.0 | 0.0 | 0.0 | 1.0 | 0.0 |
2 | 0.0 | 1.0 | 0.0 | 1.0 | 0.0 |
3 | 0.0 | 1.0 | 1.0 | 0.0 | 0.0 |
4 | 0.0 | 1.0 | 1.0 | 0.0 | 0.0 |
Както виждате от таблицата, новосъздадените колони са бинарни, а данните в тях са от тип float64.
Тази функция действа по същия начин като OneHotEncoder, но за разлика от него get_dummies() премахва оригиналните характеристики и чрез параметъра drop_first изключва първата колона от новосъздадените, за да се избегне проблемът мултиколинеарност (линейна зависимост между отделните характеристики), тъй като линейните модели предполагат, че има независимост между характеристиките.
1 2 3 4 5 |
# Кодиране на стойностите dummy_df = pd.get_dummies(X[['Sex','Housing']], drop_first=True) # Извеждане на първите 5 реда dummy_df.head() |
Резултат:
Sex_male | Housing_own | Housing_rent | |
---|---|---|---|
0 | 1 | 1 | 0 |
1 | 0 | 1 | 0 |
2 | 1 | 1 | 0 |
3 | 1 | 0 | 0 |
4 | 1 | 0 | 0 |
Като краен резултат получаваме DataFrame. Колоните са от тип uint8 за разлика от тези, които получихме при OneHotEncoder. Предимство на get_dummies() е че автоматично именува новосъздадените колони с подходящи имена за разлика от OneHotEncoder.
Използването на one hot encoding значително увеличава броя дименсии в извадката, което често е проблем за моделите, тъй като се увеличава обемът на извадката и е по-трудно графично да се представят данните.
Когато целевата променлива съдържа нечислови стойности, е необходимо те също да бъдат кодирани. Данните в този случай се трансформират по същия начин както при ordinal encoding.
LabelEncoder e част от модула preprocessing на библиотеката Scikit-learn и има същата функционалност като OrdinalEncoder, но за разлика от него, работи единствено със структура, която има само 1 колона.
1 2 3 4 5 6 7 8 |
# Извършване на трансформацията le_out = le.fit_transform(y) # Запазване на данните в нов DataFrame y_enc = pd.DataFrame(data=le_out, columns=["Risk"]) # Извеждане на първите 5 реда y_enc.head() |
Резултат:
Risk | |
---|---|
0 | 1 |
1 | 0 |
2 | 1 |
3 | 1 |
4 | 0 |
След извършване на трансформацията, за всяка една уникална нечислова стойност в променливата съответства конкретно цяло число.
Методите за трансформиране на категорийни променливи в числови не се изчерпват до тук. Има още много различни варианти за кодиране на данните. Библиотеката Category Encoders е изградена специално за тази цел. Тя е създадена на базата на Scikit-learn и предлага голямо разнообразие от възможности за кодиране на категорийни данни.
Някои от методите са например:
P(1) - вероятността на положителния клас
P(0) - вероятността на отрицателния клас
Кодиране чрез WoE ни позволява да създадем връзка между категорийната променлива и целевата. Този подход работи най-добре при модели, използващи логистична регресия, тъй като трансформираните данни са на логаритмичната скала.
Има и други методи, за които не е необходимо използването на готова библиотека, като например заместване на данните с честотата на срещане или средната стойност на целевата променлива за всяка отделна категория. Недостатък е това, че може да се получат дублиращи се стойности след заместване, което би довело до загуба на ценна информация.
Изборът на метод за кодиране на категорийни променливи зависи от това с какви данни разполагаме - дали те имат голямо разнообразие от стойности и от кой тип са - номинални или ординални. На базата на това се взима решение за това кой е подходът, който трябва да се предприеме. Ако променливите са номинални и нямат голям брой уникални стойности, може да се използва one hot encoding, а ако са ординални, да се приложи ordinal encoding. Възможно е и някой от другите методи да даде добър краен резултат. Нужно първо да се направят множество тестове, за да се прецени кой подход е най-подходящ в конкретната задача.
Включете се в курса по машинно обучение и анализ на данни с Python.