НЕТ НИКАКИХ УЧЕНЫХ-ДАННЫХ! — часть 6

Случай беспорядков большой мощности

Эта статья является частью нашей серии статей о том, как разные специалисты по обработке и анализу данных по-разному строят схожие модели. Нет одинаковых людей, и, следовательно, нет одинаковых специалистов по данным. И обстоятельства, при которых необходимо решать проблемы с данными, постоянно меняются. По этим причинам для выполнения поставленной задачи могут и будут использоваться различные подходы. В нашей серии мы рассмотрим четыре различных подхода наших специалистов по данным — Meta Oric, Aki Razzi, Andy Stand и Eqaan Librium. Перед ними ставится задача построить модель, позволяющую прогнозировать, будут ли сотрудники компании — STARDATAPEPS — искать новую работу или нет. Судя по их различным профилям, обсуждаемым в первом блоге, вы уже можете себе представить, что их подходы будут совершенно разными.

В этой статье мы рассмотрим, как работать с одним из этапов подготовки данных — категориальными переменными. Мы сравним однократное кодирование по умолчанию с несколькими альтернативами: кодирование меток, частотное кодирование, вес доказательств, плавный вес доказательств, оптимальное бинирование, выбранное вручную бинирование и удаление. Наконец, Энди и Экан решат, какой из методов они предпочли бы в своем анализе.

В предыдущей статье мы видели, как Мета и Аки строили прогностические модели. Мета сосредоточилась на том, чтобы минимизировать время, затрачиваемое на этот проект. Она выбрала мощную модель, которая практически не требует внимания к подготовке данных. Аки сделала еще один шаг вперед в своей модели, а также настроила гиперпараметры, чтобы добиться повышения точности прогнозов. В этой статье мы увидим, как Экан и Энди решат эту проблему. Из соображений объяснимости и деловых соображений они предпочитают использовать логистическую модель для прогнозирования оттока сотрудников. Это требует более обширной подготовки данных, чем то, что мы видели в предыдущих статьях. Но то, как они это сделают, будет, очевидно, отличаться.

Энди Стенд: «Понимание — это то, что мы делаем»

Энди хвалит себя за то, что может объяснить бизнесу все, что он делает. Он никогда не использует методы черного ящика и поэтому будет использовать только методы, которые могут быть легко поняты людьми без технического образования. Таким образом, некоторые из более сложных техник, которые мы сегодня рассмотрим, он не будет рассматривать для своей модели.

Eqaan Librium: «Равновесие и баланс между работой и личной жизнью»

Экан стремится достичь баланса между предсказательной силой своей модели и объяснимостью методов, поэтому он не будет уклоняться от использования более сложного метода, если он даст ему лучшую модель.

Одним из наиболее распространенных этапов подготовки является работа с категориальными переменными. Для перевода их в числовой эквивалент можно использовать различные методы. Затем их можно использовать в качестве признаков в прогностической модели. Вы видели, что Meta также использовала кодирование для подготовки всех категориальных переменных. Тем не менее, она сделала это очень примитивным способом. Она использовала один и тот же метод для всех своих переменных, даже не взглянув на то, что это за переменные. А именно, она использовала однократное кодирование для преобразования всех своих категориальных переменных.

Горячее кодирование — очень популярный метод. Это также то, с чем чаще всего сталкиваются при первой попытке построить основу всех прогностических моделей — простую линейную/логистическую регрессию. Горячее кодирование — это метод работы с номинальными переменными (или когда порядок можно игнорировать), при котором старая переменная заменяется таким количеством переменных, сколько существует категорий, и каждая категория представлена ​​в двоичном виде. Однако это не единственный доступный вариант при работе с категориальными данными, а в некоторых случаях и вовсе бесполезный.

Один конкретный случай, когда однократное кодирование становится неоптимальным, — это работа с переменными, имеющими большое количество элементов. Это переменные, которые имеют много разных значений. Затем создание отдельной переменной для каждого отдельного существующего значения может привести к существенному увеличению размера набора данных, проблемам с оценкой параметров или даже к бесконечным значениям журнала. Результатом будет модель, которую трудно обобщить на новом множестве. Не говоря уже о том, что объяснение таких результатов также может оказаться непростой задачей. В нашем наборе данных переменная city является прекрасным примером высокой кардинальности. Он имеет 123 уникальных значения.

#importing libraries:
import os
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline 
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder, LabelEncoder
from sklearn.compose import ColumnTransformer

import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
from sklearn import metrics
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import cross_val_score
from sklearn.metrics import roc_curve

# use optimal binning
from optbinning import OptimalBinning

import warnings 
warnings.filterwarnings(action= 'ignore')

Хотя, когда вы смотрите на разброс значений в деталях режима, вы можете заметить, что некоторые из них встречаются чаще, чем другие. Это, конечно, очень нормально для такой переменной, как город. В то время как в городах могут проживать миллионы человек, в малых городах будет проживать всего несколько тысяч жителей. Таким образом, более крупные города также будут чаще встречаться в наших данных. Как видно из графика ниже, переменная название города имеет много разных категорий. Результаты горячего кодирования были бы очень неудобны.

df_prep = pd.read_csv('https://bhciaaablob.blob.core.windows.net/featurenegineeringfiles/df_prepared.csv')
df = df_prep.drop(columns=['Unnamed: 0','city', 'experience', 'enrollee_id'])
plt.rcParams['figure.figsize'] = [30, 8]
plt.bar(df['city name'].value_counts().index, df['city name'].value_counts())
plt.ylabel('Number of records')
plt.xticks(rotation=90)
plt.show()

Прежде чем мы сможем перейти к построению эталонной модели, нам необходимо предварительно обработать данные для логит-модели. Ниже вы можете увидеть, как выглядят данные. У нас есть смесь категориальных и числовых переменных. Это, конечно, должно быть рассмотрено отдельно.

df.head()

# Define the target vector y
y = df['target']  

# Creating a dataset without the DV and the variable city name:
X = df.drop(['target', 'city name'], axis = 1)


# Creating an object with the column labels of only the categorical features and one with only the numeric features:
categorical_features = X.select_dtypes(exclude="number").columns.tolist()
numeric_features = X.select_dtypes(include="number").columns.tolist()

#Categorical variables
X[categorical_features] = X[categorical_features].fillna(X.mode())
one_hot_encode = OneHotEncoder(sparse=False)
one_hot_df = pd.DataFrame(one_hot_encode.fit_transform(X[categorical_features]))
one_hot_df.columns = one_hot_encode.get_feature_names(categorical_features)
    
#Numerical variables 
X[numeric_features] = X[numeric_features].fillna(X.mean())
scaler_encode = StandardScaler()
scaler_encode_df = pd.DataFrame(scaler_encode.fit_transform(X[numeric_features]))
scaler_encode_df.columns = X[numeric_features].columns                                                        

#put X back together and add the city variable
X_prep = scaler_encode_df.join(one_hot_df)
X_prep['city name'] = df['city name']

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

X_prep.head()

Горячее кодирование

Для базовой модели мы решили использовать однократное кодирование, так как это вариант по умолчанию для многих специалистов по данным. Как мы упоминали выше, идея, лежащая в основе этого, заключается в том, что каждое значение в исходной переменной становится самостоятельной переменной, где значение 1 указывает на принадлежность к категории. Вот как вы можете тренироваться и оценивать его.

#one-hot encoding city: 
enc_city = pd.get_dummies(df['city name'])

#Merge the dummmies to the df:
df_dummy = X_prep.drop('city name', axis = 1).join(enc_city)

# split X and y into training and testing sets
X_train,X_test,y_train,y_test=train_test_split(df_dummy,y,test_size=0.20,random_state=42)

# Instantiate the model (using the default parameters)
logreg = LogisticRegression()
 
# Fit the model with data
logreg.fit(X_train,y_train)

#Predict:
y_pred_dummy=logreg.predict(X_test)
y_pred_proba_dummy = logreg.predict_proba(X_test)[::,1]

# Evaluate the model with the use of cv:
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) 
scores = cross_val_score(logreg, X_test, y_test, cv=cv, scoring = 'roc_auc')
print("roc_auc = %f (%f)" % (scores.mean(), scores.std()))

Выход:

roc_auc = 0.789883 (0.019211)

Этот подход дал нам очень приличную оценку roc auc 0,7899. Хотя, может быть, мы можем добиться большего успеха с другими методами?

Существует множество методов, способных конкурировать с однократным кодированием. Таким образом, и у Экаана, и у Энди было из чего выбирать. В этой статье мы рассмотрим следующее:

  1. Кодировка этикетки
  2. Частотное кодирование
  3. Вес доказательств
  4. Гладкая масса доказательств
  5. Оптимальный биннинг
  6. Собранный вручную биннинг
  7. Удаление

В конце мы сравним их, и Энди и Экан поделятся своими мыслями о том, какие из них они выберут для этого анализа.

Кодировка этикетки

Кодирование метки — это метод, при котором каждому значению присваивается целое число, а результирующая переменная используется в анализе. Недостатком этого подхода является то, что он создает иллюзию ранжирования между категориями. Другими словами, если Нью-Йорку присваивается 1, а Бостону 2, то кажется, что Бостон больше/больше/больше, чем Нью-Йорк, хотя на самом деле этой связи не существует. Таким образом, он лучше подходит для порядковых, а не номинальных переменных. Тем не менее, мы проверим, даст ли этот метод в итоге лучшую модель.

le = LabelEncoder()

#label encoding city: 
enc_city = le.fit_transform(df['city name'])

#Merge the dummmies to the df:
df_label = X_prep.drop('city name', axis = 1)
df_label['city_enc'] = enc_city

# split X and y into training and testing sets
X_train,X_test,y_train,y_test=train_test_split(df_label,y,test_size=0.20,random_state=42)

# Instantiate the model (using the default parameters)
logreg = LogisticRegression()
 
# Fit the model with data
logreg.fit(X_train,y_train)

#Predict:
y_pred_dummy=logreg.predict(X_test)
y_pred_proba_dummy = logreg.predict_proba(X_test)[::,1]

# Evaluate the model with the use of cv on the test dataset:
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) 
scores = cross_val_score(logreg, X_test, y_test, cv=cv, scoring = 'roc_auc')
print("roc_auc = %f (%f)" % (scores.mean(), scores.std()))

Выход

roc_auc = 0.787011 (0.015378)

Кодирование метки выполняется немного хуже, чем фиктивное кодирование. Хотя разница на самом деле очень мала, этот метод определенно не следует использовать для кодирования нашей переменной, так как название города не является порядковой переменной. Кодируя его этим методом, мы создаем порядок, которого нет в данных.

Частотное кодирование

Другой интересный подход заключается в использовании в качестве информации информации о том, как часто значение появляется в наборе данных. Таким образом, мы создаем таблицу подсчета частоты, связанную с переменной высокой кардинальности, а затем вычисляем, какой процент записей имеет определенное значение. Вы также можете прочитать об этом методе здесь. Этот подход, очевидно, приводит к значениям от 0 до 1. В ячейке ниже вы можете увидеть первые 5 строк из такой кодировки. Поскольку метод очень прост для понимания, Энди был особенно рад попробовать его в регрессии.

df_fe = X_prep.groupby('city name').size()/len(X_prep)
d = pd.DataFrame()
d['Temp_freq_encode'] = X_prep['city name'].map(df_fe)
d['city name'] = X_prep['city name']
d.head()

#Merge the encoded variable to the df:
df_fe = X_prep.drop('city name', axis = 1)
df_fe['city_enc'] = d['Temp_freq_encode']

# split X and y into training and testing sets
X_train,X_test,y_train,y_test=train_test_split(df_fe,y,test_size=0.20,random_state=42)

# Instantiate the model (using the default parameters)
logreg = LogisticRegression()
 
# Fit the model with data
logreg.fit(X_train,y_train)

#Predict:
y_pred_dummy=logreg.predict(X_test)
y_pred_proba_dummy = logreg.predict_proba(X_test)[::,1]

# Evaluate the model with the use of cv on the test dataset:
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) 
scores = cross_val_score(logreg, X_test, y_test, cv=cv, scoring = 'roc_auc')
print("roc_auc = %f (%f)" % (scores.mean(), scores.std()))

Выход:

roc_auc = 0.793895 (0.016094)

Ну разве это не прекрасный результат! Кодирование частоты превзошло нашу базовую модель (0,794 против 0,7899). Хотя разница невелика, включение информации о том, как часто значение появляется в данных, приводит к улучшению показателя AUC. Этот метод также очень легко объяснить, что делает его хорошим претендентом на окончательное решение Энди.

Вес доказательств

Вес доказательств (WOE) измеряет «силу» переменной по отношению к цели. Вес свидетельствует о том, насколько хорошо «события» и «не-события» могут быть отделены друг от друга. Для категориальной переменной вы можете рассчитать WOE для каждого категориального значения. Все WOE вместе образуют одну новую числовую переменную, вычисляемую следующим образом:

Это значение будет положительным, если процент событий выше, чем процент несобытий, и, следовательно, отрицательным, если процент событий меньше, чем процент несобытий. На самом деле вес доказательств показывает, насколько значение поддерживает или опровергает гипотезу.
После расчета значения WOE для каждой отдельной группы мы сопоставляем его с исходными данными, а затем используем в качестве переменной в нашей модели.

Этот подход создает переменную, которая уже преобразована в логистическую шкалу, тем самым улучшая модель. Подробнее об этом подходе можно прочитать в этом большом обзоре.

def calcWoes(df, catvar, target):
    rho = df[target].mean()
    c = 25
    logit = np.log(rho/(1.0-rho)) # mean logit
    catvalues = df[catvar].unique()

    # initialise my dataframe
    Woe_frame = pd.DataFrame()

    # calculate Woes
    for catvalue in catvalues:
        n = df.loc[(df[catvar] == catvalue)][target].count()
        n_nonevents = df.loc[(df[catvar] == catvalue) &
(df[target] == 0)][target].count()
        n_events    = df.loc[(df[catvar] == catvalue) &
(df[target] == 1)][target].count()
        Woe = np.log(np.maximum(n_events,0.5)/np.maximum(n_nonevents,0.5))
        Woe_frame = Woe_frame.append([[catvalue,Woe]])

    # rename columns
    Woe_frame = Woe_frame.rename(columns={0:catvar,1:'Woe'})
    return (Woe_frame, logit)

def Woe_enrich(df, Woe_frame, logit, catvar):   
    result = pd.merge(df, Woe_frame, on=[catvar], how ='left')
    result.loc[(result['Woe'].isnull()),'Woe'] = logit
    return result
# split X and y into training and testing sets
X_train,X_test,y_train,y_test=train_test_split(X_prep,y,test_size=0.20,random_state=42)
df_woe_train = X_train.join(y_train)
# calculate Woe's
Woe, logit = calcWoes(df_woe_train,'city name', 'target')


# Add encoded variable
df_ver_Woe_train = Woe_enrich(X_train, Woe, logit, 'city name').drop('city name', axis = 1)
df_ver_Woe_test = Woe_enrich(X_test, Woe, logit, 'city name').drop('city name', axis = 1)


# Instantiate the model (using the default parameters)
logreg = LogisticRegression()
 
# Fit the model with data
logreg.fit(df_ver_Woe_train,y_train)

#Predict:
y_pred_dummy=logreg.predict(df_ver_Woe_test)
y_pred_proba_dummy = logreg.predict_proba(df_ver_Woe_test)[::,1]


# Evaluate the model with the use of cv on the test dataset:
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) 
scores = cross_val_score(logreg, df_ver_Woe_test, y_test, cv=cv, scoring = 'roc_auc')
print("roc_auc = %f (%f)" % (scores.mean(), scores.std()))

Выход:

roc_auc = 0.792368 (0.013413)

Не совсем так хорошо, как кодирование частоты, но очень достойный результат. Хотя этот прием может и не для Энди, так как он не очень интерпретируемый. Однако для Eqaan это явный соперник.

Гладкая масса доказательств

Как мы только что видели, WOE — отличная техника. Однако у него есть один существенный недостаток при работе с переменной высокой кардинальности. А именно, когда количество (не)событий в группе равно нулю или близко к нулю, значение WOE будет чрезвычайно большим или даже бесконечным. Это создает неустойчивые решения. Хитрость заключается в том, чтобы исправить это. Есть два способа сделать это:

  • Вы можете объединять группы с похожим WOE. Это приводит вас к оптимальному биннингу, который мы рассмотрим вкратце.
  • Вы можете настроить WOE или log(шансы), добавив небольшое количество (не)событий, которые отражают средние шансы, тем самым сгладив соотношение редких и обычных значений. Этот подход сглаженной массы доказательств (SWOE) является более надежным. Мы тестируем его ниже.
def calcSwoes(df, catvar, target):
    rho = df[target].mean()
    c = 25
    logit = np.log(rho/(1.0-rho)) # mean logit
    catvalues = df[catvar].unique()

    # initialise my dataframe
    swoe_frame = pd.DataFrame()

    # calculate swoes
    for catvalue in catvalues:
        n = df.loc[(df[catvar] == catvalue)][target].count()
        #c_sub = np.maximum(0, (c - n))
        n_nonevents = df.loc[(df[catvar] == catvalue) &
 (df[target] == 0)][target].count()
        n_events    = df.loc[(df[catvar] == catvalue) &
 (df[target] == 1)][target].count()
        swoe = np.log((n_events + (c * rho))/(n_nonevents + (c *(1.0-rho))))
        swoe_frame = swoe_frame.append([[catvalue,swoe]])

    # rename columns
    swoe_frame = swoe_frame.rename(columns={0:catvar,1:'swoe'})
    return (swoe_frame, logit)


def swoe_enrich(df, swoe_frame, logit, catvar):   
    result = pd.merge(df, swoe_frame, on=[catvar], how ='left')
    result.loc[(result['swoe'].isnull()),'swoe'] = logit
    return result
# split X and y into training and testing sets
X_train,X_test,y_train,y_test=train_test_split(X_prep,y,test_size=0.20,random_state=42)
df_swoe_train = X_train.join(y_train)
# calculate Woe's
Woe, logit = calcSwoes(df_swoe_train,'city name', 'target')


# Add encoded variable
df_ver_SWoe_train = swoe_enrich(X_train, Woe, logit, 'city name').drop('city name', axis = 1)
df_ver_SWoe_test = swoe_enrich(X_test, Woe, logit, 'city name').drop('city name', axis = 1)


# Instantiate the model (using the default parameters)
logreg = LogisticRegression()
 
# Fit the model with data
logreg.fit(df_ver_SWoe_train,y_train)

#Predict:
y_pred_dummy=logreg.predict(df_ver_SWoe_test)
y_pred_proba_dummy = logreg.predict_proba(df_ver_SWoe_test)[::,1]


# Evaluate the model with the use of cv on the test dataset:
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) 
scores = cross_val_score(logreg, df_ver_SWoe_test, y_test, cv=cv, scoring = 'roc_auc')
print("roc_auc = %f (%f)" % (scores.mean(), scores.std()))

Выход:

roc_auc = 0.792714 (0.012734)

Удивительно, но сглаженная масса доказательств на самом деле оказалась немного хуже, чем масса доказательств. Но разница невелика, а именно около 0,001. Таким образом, похоже, что эти два метода дают почти идентичные результаты в нашем случае.

Собранный вручную биннинг

Один из самых интуитивно понятных способов — попытаться уменьшить мощность переменной вручную. Логика этого подхода заключается в том, что, хотя по отдельности все значения, которые появляются в данных только один или два раза, могут не нести много информации, их объединение может улучшить прогностическую способность. Не говоря уже о том, что это значительно улучшит интерпретируемость, поскольку вам больше не нужно описывать все отдельные значения. Однако подводный камень этого подхода заключается в том, что вы сами будете определять, где именно находится отсечка. Это, конечно, может сделать любой график красивым, но статистическая основа такого действия довольно расплывчата.

keep_values = X_prep['city name'].value_counts().loc[X_prep['city name'].value_counts()>50].index

#all cities that are not in the list, combine
def re_code_city(x):
  if x not in(keep_values):
    return 'Combined_city'
  else:
    return x
#recode and attach the new variable
recoded_city = X_prep['city name'].apply(re_code_city)

#one-hot encoding city: 
enc_city = pd.get_dummies(recoded_city)

#Merge the dummmies to the df:
df_label = X_prep.drop(['city name'], axis = 1).join(enc_city)

# split X and y into training and testing sets
X_train,X_test,y_train,y_test=train_test_split(df_label,y,test_size=0.20,random_state=42)

# Instantiate the model (using the default parameters)
logreg = LogisticRegression()
 
# Fit the model with data
logreg.fit(X_train,y_train)

#Predict:
y_pred_dummy=logreg.predict(X_test)
y_pred_proba_dummy = logreg.predict_proba(X_test)[::,1]

# Evaluate the model with the use of cv:
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) 
scores = cross_val_score(logreg, X_test, y_test, cv=cv, scoring = 'roc_auc')
print("roc_auc = %f (%f)" % (scores.mean(), scores.std()))

Выход:

roc_auc = 0.790029 (0.019287)

Трудно спорить, этот метод очень интуитивен, и Энди будет очень легко объяснить другим, что он сделал. Однако результат все равно не дотягивает до нашей модели с частотным кодированием. Но может быть, если бы биннинг был сделан более изощренно, мы могли бы добиться лучшего результата?

Оптимальный биннинг

Оптимальное бинирование — это оптимальное разбиение переменной на ячейки с заданным числовым значением. Это процесс, в котором значения переменной группируются в ячейки с аналогичным целевым поведением. Это объединение выполняется таким образом, чтобы иметь оптимальное соотношение между новой переменной с интервалами и целью. Вы можете добавить ограничения, такие как количество бинов или размер бина.

Увидеть, какие значения «соответствуют друг другу», может быть очень полезно для понимания взаимосвязи между переменной и целью. Такие проблемы, как пропущенные значения или выбросы, устраняются, они помещаются в одну из корзин. Переменная binned имеет гораздо меньше категорий, поэтому после горячего кодирования намного меньше фиктивных переменных.

# split X and y into training and testing sets
X_train,X_test,y_train,y_test=train_test_split(X_prep,y,test_size=0.20,random_state=42)

# 1) Define your feature and target arrays
X_var = X_prep['city name']

# 2) Instantiate class and fit to train dataset
optb = OptimalBinning(name='city name', dtype="categorical" ,solver="cp")#oke, min_prebin_size=0.01,max_n_prebins=50,gamma=0) #min_n_bins=3,max_n_bins=3,min_prebin_size=0.05)
optb.fit(X_train['city name'], y_train)

# 3) Show binning table
binning_table = optb.binning_table
binning_table = binning_table.build()

# 4) To perform the binning of a dataset
x_transform_woe = optb.transform(X_var, metric="woe")
X_with_Woe = pd.DataFrame({'city name': X_var, "WoE": x_transform_woe})
# dropping ALL duplicate values
X_with_Woe.drop_duplicates(inplace = True)

X_train_optbin = pd.merge(X_train, X_with_Woe, on=['city name'], how ='left')
X_test_optbin = pd.merge(X_test, X_with_Woe, on=['city name'], how ='left')

# 5) To visualize the results table and plot
optb.binning_table.build()
optb.binning_table.plot(metric="woe")

# Instantiate the model (using the default parameters)
logreg_optbin = LogisticRegression()

# Fit the model with data
# logreg_optbin.fit(X_train,y_train)

# df.loc[:, df.columns != col]
X_train_optbin2 = X_train_optbin.drop(['city name'], axis = 1)
X_test_optbin2 = X_test_optbin.drop(['city name'], axis = 1)

from sklearn import preprocessing
scaler = preprocessing.StandardScaler().fit(X_train_optbin2)
X_train_scaled = scaler.transform(X_train_optbin2)
X_test_scaled = scaler.transform(X_test_optbin2)

# Fit the model with data
logreg_optbin.fit(X_train_scaled,y_train)

#Predict:
y_pred_optbin = logreg_optbin.predict(X_test_scaled)
y_pred_proba_optbin = logreg_optbin.predict_proba(X_test_scaled)[::,1]

#Getting its model coefficients:
#df_model_coef_optbin = pd.DataFrame(logreg_optbin.coef_[0,:], X_train_optbin2.columns)
#print(df_model_coef_optbin)


# Evaluate the model with the use of cv on the test dataset:
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) 
scores = cross_val_score(logreg, X_test_optbin2, y_test, cv=cv, scoring = 'roc_auc')
print("roc_auc = %f (%f)" % (scores.mean(), scores.std()))

Выход:

roc_auc = 0.792142 (0.012905)

Как и предполагалось, это лучше, чем ручное объединение категорий, однако в целом эта модель не самая лучшая!

Удаление

Есть и другой подход, а именно полное удаление переменной высокой кардинальности. Хотя это очень быстрое решение, мы хотели бы призвать вас не делать этого. Переменная может оказаться важной для модели, которую вы пытаетесь построить. Его удаление может привести к потере ценной информации. Однако в наших данных уже есть переменная, несущая информацию о городе, а именно индекс развития города. Так что, возможно, здесь это будет приемлемым решением. Очевидно, что мы не рассчитываем получить потрясающие результаты, используя этот метод.

#Drop the city name variable altogether
df_drop = X_prep.drop(['city name'], axis = 1)

# split X and y into training and testing sets
X_train,X_test,y_train,y_test=train_test_split(df_drop,y,test_size=0.20,random_state=42)

# Instantiate the model (using the default parameters)
logreg = LogisticRegression()
 
# Fit the model with data
logreg.fit(X_train,y_train)

#Predict:
y_pred_dummy=logreg.predict(X_test)
y_pred_proba_dummy = logreg.predict_proba(X_test)[::,1]

# Evaluate the model with the use of cv:
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) 
scores = cross_val_score(logreg, X_test, y_test, cv=cv, scoring = 'roc_auc')
print("roc_auc = %f (%f)" % (scores.mean(), scores.std()))

Выход:

roc_auc = 0.779074 (0.014153)

Неудивительно, что соответствие модели хуже, чем у нашего базового уровня. Однако разница несущественна, и если бы хотелось сэкономить время на обработке данных, эту переменную можно было бы убрать.

В этой статье мы рассмотрели различные методы работы с категориальными переменными и то, как они работают, особенно если переменная имеет большое количество элементов. В целом мы увидели, что все методы дали очень сопоставимые результаты.

Давайте теперь отдадим микрофон Энди и Экаану, что они обо всем этом думают.

«Лично я бы использовал частотное кодирование для работы с переменной названия города. Это очень легко объяснить. Мне особенно нравится, что вы не только получаете очень хорошую модель, но и получаете больше информации о распределении самой переменной. Может быть, мы даже можем подумать о каких-то новых вмешательствах, основанных на местонахождении наших сотрудников? Возможно, какие-то интересные местные события?

«Частотное кодирование было явным победителем. Я бы сказал, что гладкая масса доказательств также была хорошим подходом. Он показал себя немного хуже. Я также хотел бы добавить, что мы видим, что — за исключением частотного кодирования — менее интерпретируемые методы работают лучше, чем те, которые предпочел бы Энди».

Эта статья является частью нашей серии статей Ни один специалист по данным не похож на другого. Полную серию написали Аня Тонне, Юрриан Нагелькерке, Карин Груйс-Водде и Том Бланке. Серия также доступна на theanalyticslab.nl.

Обзор всех статей на Medium из серии:

  1. Представляем наших рок-звезд в науке о данных
  2. Данные для прогнозирования того, какие сотрудники могут уйти
  3. Хорошая модель по умолчанию с использованием XGBoost
  4. Настройка гиперпараметров для гиперточной модели XGBoost
  5. Бей грязные данные
  6. Дело о беспорядках большой мощности
  7. Руководство по работе с отсутствующими данными
  8. Визуализируйте бизнес-ценность прогностических моделей
  9. Ни один Data Scientist не похож на другой!

Вы хотите сделать это сами? Пожалуйста, не стесняйтесь загружать блокнот на нашей странице gitlab.