Machine Learning: 5 начина да конструираме нови променливи

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

Какви резултати ще получим от модела за машинно обучение зависи от това какви входни данни ще му подадем. Конструирането на нови променливи е един ефективен начин, чрез който можем да подобрим работата на моделите, като за тази цел е необходима и известна доза креативност.

За да можем да конструираме нови характеристики са нужни знания за конкретната предметна област – да знаем от къде идват данните и какво би било полезно, за да можем да решим конкретната бизнес задача. Нужно е да се премине през множество експерименти по време на този етап докато се създадат най-релевантните характеристики.

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

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

InvoiceNoStockCodeDescriptionQuantityInvoiceDateUnitPriceCustomerIDCountry
53642384029ERED WOOLLY HOTTIE HEART.812/1/2010 12:083.7518085United Kingdom
53653222962JAM JAR WITH PINK LID1212/1/2010 13:240.8512433Norway
53640184625APINK NEW BAROQUECANDLESTICK CANDLE312/1/2010 11:212.9515862United Kingdom
53678421499BLUE POLKADOT WRAP5012/2/2010 15:200.3415061United Kingdom
C53682221430SET/3 RED GINGHAM ROSE STORAGE BOX-112/2/2010 17:193.7514625United Kingdom

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

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

Конструирането на нови променливи оказва голямо влияние върху работата на модела за машинно обучение и също позволява по-качествено да анализираме данните, с които разполагаме.

В примерите по-надолу ще видим как можем да:

  • създаваме изчислени колони чрез комбиниране на 2 или повече характеристики
  • разделяме съществуващи променливи
  • извличаме допълнителни данни от съществуващи характеристики
  • създаваме бинарни променливи
  • създаваме нови характеристики чрез агрегиране на данните

Комбиниране на характеристики

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

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

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

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

# Създаване на калкулирана променлива за общите продажби
df['TotalSales'] = df.apply(lambda x: x.Quantity * x.UnitPrice 
                            if x.Quantity > 0 
                            else 0,
                            axis=1)

Ето така изглеждат 5 случайни реда от новата характеристика:

TotalSales
10.2
0.65
14.75
3.3
0.0

Разделяне на съществуващи променливи

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

В нашия пример характеристиката, която съдържа описанието на продуктите в онлайн магазина, има в себе си допълнителна информация, отнасяща се до цвета на артикула. Във фрагментите от код по-надолу ще я разделим, като в новата характеристика ще съхраним наименованията на цветовете за всеки продукт. За тези артикули, при които не е посочен конкретен цвят в описанието, ще зададем \’NOT SPECIFIED\’

# Създаване на променлива за цветовете, които ще се използват в регулярния израз
colors = 'WHITE|BLACK|RED|YELLOW|GREEN|ORANGE|PURPLE|BLUE|BROWN|PINK|GREY|GRAY|IVORY|CHOC|SILVER|CREAM'

# Създаване на празен масив, в който ще се съхраняват наименования на цветовете
color_col = []

# Откриване на наименованията на цветовете в описанието на продуктите
for index, row in df[['Description']].iterrows():
    find_color = re.search(r'(^|\s+)(?:' + colors + ')(\s+|\/(?:' + colors + ')\s+|\+(?:' + colors + ')\s+|$|)', row['Description'])
    if find_color:
        color_col.append(find_color.group(0).strip())
    else:
        color_col.append('NOT SPECIFIED')

# Запазване на наименованията на цветовете в нова променлива
df['Color'] = color_col
ColorCount
RED388
WHITE187
PINK154
BLUE100
BLACK73
GREEN66
GREY22
NOT SPECIFIED19
YELLOW14
ORANGE11
IVORY10
PURPLE9
BLACK/BLUE5
CREAM3
BROWN3
BLACK+SILVER2
RED/WHITE2
IVORY/RED2
CHOC+BLUE1
BLUE/CREAM1
PINK/WHITE1
PINK/YELLOW1
BLACK+WHITE1
WHITE/RED1

Можете да видите, че най-много закупени продукти са с червен цвят – 388 на брой, а най-малко са тези, където има комбинация от 2 цвята – по 1-2 закупени продукти.

След като сме запазили наименованията на цветовете в новата променлива, те съвсем спокойно могат да бъдат премахнати от колоната с описанието.

# Премахване на наименованията на цветовете от колоната с описанията
for i in df['Color'].values:
    df['Description'] = df['Description'].apply(lambda x: x.replace(i,''))

Данните след обработката изглеждат по следния начин:

DescriptionColor
METAL LANTERNWHITE
JUMBO BAG POLKADOTPINK
FLOCK LOVE HEART PHOTO FRAMERED
LARGE HONEYCOMB PAPER BELLWHITE
KITCHEN SCALESRED

Извличане на допълнителни данни от съществуващи променливи

Понякога данните може да не са в идеалния формат и да е по-добре, ако са представени по по-различен начин. Например ако имаме дата или ЕГН, бихме могли да извлечем от тях допълнителни данни като месец, година, възраст и т.н. и да ги съхраним в нови променливи.

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

# Смяна на типа на колоната InvoiceDate в datetime
df['InvoiceDate'] = pd.to_datetime(df['InvoiceDate'])

# Извличане на ден, месец, година и час от дата и запазване в отделни променливи
df['Day'] = df['InvoiceDate'].dt.day
df['Month'] = df['InvoiceDate'].dt.month
df['Year'] = df['InvoiceDate'].dt.year
df['Time'] = df['InvoiceDate'].dt.time
DayMonthYearTime
212201011:21:00
112201016:21:00
112201011:45:00
112201011:49:00
212201014:40:00

Като имаме вече отделни променливи за ден, месец, година и час, можем да ги използваме по време на етапа на предварителен анализ, за да добием по-добра представа за данните.

Създаване на бинарни променливи

Можем да отделим ключова информация за данните, като създаваме бинарни променливи. По този начин помагаме на модела да намери това, което е по-важно.

В нашия пример ще създадем 2 бинарни променливи.

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

# Създаване на променлива за местни купувачи и такива в чужбина
df['IsLocal'] = df.apply(lambda x: 1 if x.Country == 'United Kingdom' else 0, axis = 1)

Новата променлива изглежда по следния начин:

IsLocal
1
1
1
1
0

Другата нова променлива – IsCancelled е свързана с покупките на клиентите и показва дали дадена поръчка е отказана или не.

# Създаване на променлива за отказани поръчки
df['IsCancelled'] = df['InvoiceNo'].apply(lambda x: 1 if 'C' in x else 0 )

Променливата изглежда така:

IsCancelled
0
0
0
0
1

Агрегиране

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

В нашия пример ще създадем 4 нови характеристики, като ще групираме данните по CustomerID и по InvoiceNo.

Първата – NumOrders ще я групираме само на ниво CustomerID, за да бъде изчислен общият брой фактури за всеки отделен клиент.

# Изчисляване на броя поръчки
df_orders = df.groupby('CustomerID', as_index=False)['InvoiceNo'].count()

# Преименуване на колоната
df_orders.rename(columns={'InvoiceNo': 'NumOrders'}, inplace=True)

# Обединяване на df_orders с оригиналния DataFrame
df = pd.merge(df, df_orders, on='CustomerID')

Останалите 3 нови променливи – TotalOrdersAmount, MinOrderAmount и MaxOrderAmount ще групираме по 2 колони – CustomerID и InvoiceNo, за да можем да изчислим общата сума на всяка фактура за конкретен клиент, както и неговата най-ниска и най-висока по стойност поръчка.

# Групиране на данните по CustomerID и InvoiceNo и използване на агрегиращи функции
df_temp = df.groupby(by=['CustomerID','InvoiceNo'], as_index=False)['TotalSales'].agg(['sum', 'min', 'max'])

# Смяна на имената на колоните
df_temp.rename(columns={'sum':'TotalOrdersAmount', 'min':'MinOrderAmount', 'max':'MaxOrderAmount'}, inplace=True)

# Нулиране на индекса
df_temp.reset_index(inplace=True)

# Обединяване на df_temp с оригиналния DataFrame
df = df.merge(df_temp, left_on=['CustomerID','InvoiceNo'], right_on=['CustomerID','InvoiceNo'])

Новите характеристики изглеждат по следния начин:

CustomerIDInvoiceNoNumOrdersTotalOrdersAmountMinOrderAmountMaxOrderAmount
155745367962580.480.298.5
17802536747534.353.7512.6
162445366382031010.222.5
15363536698350.4514.7519.8
1430753640811176.8510.222.5

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

Извод

Като създаваме нови характеристики можем открием определени зависимости в данните и да получим по-точни резултати от моделите. По какъв начин ще конструираме новите характеристики зависи от данните, които имаме, както и от конкретната бизнес задача, която се опитваме да решим.

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

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

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

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