Прогноз покупки страховки клиентами туроператора
Выпускница OTUS
Всем привет! В этом посте хочу рассказать о своем проекте, в котором я попыталась сделать прогноз покупки страховки клиентами туроператора методами ML, изученными на курсе Machine Learning. Basic от образовательного ресурса для IT-специалистов OTUS.
Данные я брала с Kaggle: https://www.kaggle.com/datasets/sellingstories/travel-company-insurance-prediction.
База данных показалась мне интересной по двум причинам:
Во-первых, по ссылке располагались два датасета: один с целевым признаком, второй — без него.
Во-вторых, при беглом просмотре датасета стало понятно, что задача не такая уж и простая, потому что при небольшом датасете признаки имеют низкую корреляцию, а значит для получения более высоких метрик нужно постараться в подборе модели.
Я подумала, что это неплохой шанс не только закрепить полученные знания, но и попробовать свои силы в написании статьи, после чего приступила к работе. Язык разработки Python, среда разработки Jupiter Notebook.
Используя первый датасет с целевым признаком, я обучила 10 моделей. Для каждой модели делала подбор гиперпараметров на кроссвалидации с помощью градиентного бустинга. Сравнивая метрики (f1-score, auc-roc-score), я выбрала оптимальную модель, в которую подала второй датасет без целевого признака для предсказания покупки страховки.
Необходимо сказать, что в связи с ограниченностью первого датасета и низкой корреляцией признаков, все мои ухищрения по работе с датасетом и подбору гиперпараметров, не привели к получению высоких метрик. В связи с этим, цель работы была выполнена — знания закреплены, но осталось некоторое сожаление о том, что не получилось зафиксировать высокие метрики. Поэтому для себя я сделала вывод, что для более успешных показателей необходим более обширный датасет, либо по количеству значений, либо по дополнительным признакам.
Работа с датасетом №1
Импортируем необходимые библиотеки:
import numpy as np
import numpy.linalg as la
import matplotlib
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
sns.set_style('darkgrid')
import plotly.express as px
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from sklearn import datasets
from sklearn import preprocessing
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
%matplotlib inline
np.random.seed(123)
import warnings
warnings.filterwarnings("ignore")
Загружаем датасет и делаем анализ. Датасет содержит 9 признаков, среди них есть категориальные.
data = pd.read_csv('Travel_Company_Old_Clients.csv', sep = ';')
data.head()
Описание признаков
Age — Age Of The Customer;
Employment Type — The Sector In Which Customer Is Employed;
GraduateOrNot — Whether The Customer Is College Graduate Or Not;
AnnualIncome — The Yearly Income Of The Customer In Indian Rupees;
FamilyMembers — Number Of Members In Customer’s Family;
ChronicDisease — Whether The Customer Suffers From Any Major Disease Or Conditions Like Diabetes/High BP or Asthama, etc.;
FrequentFlyer — Derived Data Based On Customer’s History Of Booking Air Tickets On Atleast 4 Different Instances In The Last 2 Years 2017–2019;
EverTravelledAbroad — Has The Customer Ever Travelled To A Foreign Country;
TravelInsurance (1st File Only) — Did The Customer Buy Travel Insurance Package During Introductory Offering.
Проверяем размер датасета: 682 строчки, 9 столбцов.
data.shape
Датасет содержит 4 категориальных признака. Нет пропущенных значений.
data.info()
data.isnull().sum()
Датасет содержит 113 дубликатов. Удаляем дубликаты, после этого размер датасета становится 569 строк, количество столбцов без изменений.
data.duplicated().sum()
data.drop_duplicates(inplace = True)
data = data.reset_index(drop = True)
data.info()
Визуализируем признаки.
data.hist(figsize=(20, 20))
Более детально посмотрим на каждый признак и его детализацию по таргету. Датасет содержит клиентов в возрастном диапазоне от 25 до 35 лет. Наиболее частое значение 28 лет, в основном они не покупают страховку.
sns.countplot(x = data['Age'], hue=data['TravelInsurance'])
# структура признака, выраженная в %
data['Age'].value_counts(normalize=True)*100
84% клиентов имеют высшее образование, при этом как люди с образованием, так и без предпочитают обходиться без страховки.
sns.countplot(x = data['GraduateOrNot'],hue=data['TravelInsurance'])
data['GraduateOrNot'].value_counts(normalize=True)*100
Страховку предпочитают покупать клиенты с более высоким уровнем дохода.
sns.countplot(x = data['AnnualIncome'],hue=data['TravelInsurance'])
plt.xticks(rotation = 90)
plt.show()
data['AnnualIncome'].value_counts(normalize=True)*100
Наиболее часто состав семьи представляет от 3 до 5 человек. Не прослеживается сильной взаимосвязи между составом семьи и покупкой страховки.
sns.countplot(x = data['FamilyMembers'],hue=data['TravelInsurance'])
data['FamilyMembers'].value_counts(normalize=True)*100
Вне зависимости от наличия или отсутствия хронических заболеваний люди предпочитают обходиться без страховки.
sns.countplot(x = data['ChronicDiseases'],hue=data['TravelInsurance'])
data['ChronicDiseases'].value_counts(normalize=True)*100
77% клиентов не пользуются услугами авиакомпаний. Большинство тех, кто путешествует на самолете, покупают страховку.
sns.countplot(x = data['FrequentFlyer'],hue=data['TravelInsurance'])
data['FrequentFlyer'].value_counts(normalize=True)*100
80% клиентов совершают поездки внутри страны. Подавляющее большинство тех, кто путешествует заграницу, покупают страховку.
sns.countplot(x = data['EverTravelledAbroad'],hue=data['TravelInsurance'])
data['EverTravelledAbroad'].value_counts(normalize=True)*100
73% клиентов работают в негосударственном секторе. Они чаще покупают страховку, чем клиенты, работающие в госкорпорациях.
sns.countplot(x = data['Employment Type'],hue=data['TravelInsurance'])
data['Employment Type'].value_counts(normalize=True)*100
Покупка страховки — целевой признак. 64% клиентов страховку не покупают.
sns.countplot(x = data['TravelInsurance'],hue=data['TravelInsurance'])
data['TravelInsurance'].value_counts(normalize=True)*100
Также мне нравится анализировать признаки с помощью plotly. В частности, можно сразу посмотреть распределение признака, частоту значений, квартили, наличие/отсутствие выбросов. Приведу для примера несколько графиков. Видно, что признаки из датасета не содержат выбросы, значит датасет не нуждается в дополнительной работе над аномалиями.
fig = px.histogram(data, x='Age', color='TravelInsurance', marginal = 'box')
fig.show()
fig = px.histogram(data, x='AnnualIncome', color='TravelInsurance', marginal = 'box')
fig.show()
fig = px.histogram(data, x='FamilyMembers', color='TravelInsurance', marginal = 'box')
fig.show()
Иногда бывают полезны графики, построенные с помощью seaborn. Они позволяют отразить сразу несколько признаков. Также приведу пару примеров построения.
Большинство клиентов туроператора не путешествуют заграницу, при этом те, кто предпочитает ездить внутри страны, покупают страховку вне зависимости от возраста и дохода. Клиенты, пересекающие границу, приобретают страховку вне зависимости от возраста и при более высоком уровне дохода.
sns.relplot(data=data, x='AnnualIncome', y='Age', hue='TravelInsurance', col='EverTravelledAbroad', palette='bright', height=4)
Другой пример позволяет посмотреть факт покупки страховки в зависимости от наличия/отсутствия хронических заболеваний, возраста, уровня дохода.
sns.relplot(data=data, x='AnnualIncome', y='Age', hue='TravelInsurance', col='ChronicDiseases', palette='bright', height=4)
Даже по представленным выше графикам видно, что признаки обладают низкой корреляцией. Но мы дополнительно это проверим позже с помощью определенных методов.
А сейчас поработаем с категориальными признаками. Сначала проверим вариабельность значений каждого категориального признака, чтобы определиться со способом перекодировки. Видим, что первые три признака содержат варианты Yes/No, значит их можно перекодировать в бинарный формат 1/0.
Признак Employment Type также содержит два уникальных значения, но частота вхождения этих значений сильно отличается: 149 значений Government Sector и 420 значений Private Sector/Self Employed. Если мы просто перекодируем по частоте вхождения, то из-за несбалансированности значений, модель может неверно интерпретировать и придать более частому значению больший вес. Чтобы избежать этой неверной интерпретации моделью перекодируем последний признак с помощью One Hot Encoding.
data['GraduateOrNot'].unique()
data['FrequentFlyer'].unique()
data['EverTravelledAbroad'].unique()
data['Employment Type'].unique()
print(data['Employment Type']. value_counts ()['Government Sector'])
print(data['Employment Type']. value_counts ()['Private Sector/Self Employed'])
data['GraduateOrNot'] = data['GraduateOrNot'].apply(lambda x: 1 if x=='Yes' else 0)
data['FrequentFlyer'] = data['FrequentFlyer'].apply(lambda x: 1 if x=='Yes' else 0)
data['EverTravelledAbroad'] = data['EverTravelledAbroad'].apply(lambda x: 1 if x=='Yes' else 0)
Используем One Hot Encoding для признака Employment Type. После этого проверим результат всех перекодировок с помощью head()
.
from category_encoders import OneHotEncoder
enc = OneHotEncoder()
enc.fit_transform(data[['Employment Type']]).head()
data = data.drop(['Employment Type'], axis = 1).join(enc.fit_transform(data[['Employment Type']], axis = 0))
data.head()
Проверим статистические показатели и взаимосвязь признаков. Как видим из матрицы корреляций и тепловой карты, признаки коррелируют очень слабо. Есть единичные признаки с условно средним уровнем корреляции.
data.describe()
data.corr()
plt.subplots(figsize=(10,7))
sns.heatmap(data.corr(), cbar=True, annot=True, square=True, fmt='.2f', annot_kws={'size': 10},\
cmap=sns.color_palette("coolwarm", 10000), vmin=-1, center=0)
plt.show()
Датасет почти готов к работе с моделью. Разобьем его на X и у.
X = data.copy()
X.drop(['TravelInsurance'], axis=1, inplace=True)
y = data['TravelInsurance']
Нормализуем данные и проверим размер выборок train
, test
.
-----------------------------------------------------------------------------------------------------------------------------------------
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 42)
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
X_train.shape, X_test.shape, y_train.shape, y_test.shape
Для оценки качества обучения будем использовать метрики: accuracy_score
, recision_score
, recall_score
, f1_score
, roc_auc_score
. При этом в основном будем ориентироваться на f1_score
и roc_auc_score
, как более показательные.
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
def quality_report(prediction, actual):
print("Accuracy: {:.3f}\nPrecision: {:.3f}\nRecall: {:.3f}\nf1_score: {:.3f}".format(
accuracy_score(prediction, actual),
precision_score(prediction, actual),
recall_score(prediction, actual),
f1_score(prediction, actual)
))
Обучаем модель методом ближайших соседей. После этого проверим качество обучения на ранее выбранных метриках.
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=13)
knn.fit(X_train, y_train)
knn_predictions_y = knn.predict(X_test)
-----------------------------------------------------------------------------------------------------------------------------------------
print("Train quality:")
quality_report(knn.predict(X_train), y_train)
print("\nTest quality:")
quality_report(knn.predict(X_test), y_test)
Построим матрицу ошибок.
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, knn_predictions_y)
cm
Для визуального отображения строим график Roc- Auc, а также рассчитаем roc_auc_score
.
from sklearn.metrics import roc_auc_score
import sklearn.metrics as metrics
from sklearn.metrics import roc_curve
def roc_auc(model, X_test, y_test ):
y_scores = model.predict_proba(X_test)
fpr, tpr, thresh = roc_curve(y_test, y_scores[:,1], pos_label = 1)
roc_auc = metrics.auc(fpr, tpr)
plt.title('Receiver Operating Characteristic')
plt.plot(fpr, tpr, 'b', label = 'AUC = %0.2f' % roc_auc)
plt.legend(loc = 'lower right')
plt.plot([0, 1], [0, 1],'r--')
plt.ylabel('True Positive Rate')
plt.xlabel('False Positive Rate')
plt.show()
roc_auc(knn, X_test, y_test )
Вот он, момент истины… Получаем не самые высокие метрики.
Пробуем подобрать гиперпараметры на кроссвалидации с помощью метода градиентного спуска. Получаем лучшее значение k = 13.
from sklearn.model_selection import GridSearchCV
def grid_optimization(model, parameters, X_train, y_train, X_test):
gs = GridSearchCV(model, # Classifier object to optimize
parameters, # Grid of the hyperparameters
scoring='accuracy', # Claasification quality metric to optimize
cv=5, # Number of folds in KFolds cross-validation (CV)
n_jobs=-1,
verbose=True
)
# Run Grid Search optimization
gs.fit(X_train, y_train)
print('Best parameters: ', gs.best_params_)
print('Best Accuracy Through Grid Search : {:.3f}'.format(gs.best_score_))
knn = KNeighborsClassifier()
# Estimate grid of the classifier hyperparameters
parameters = {'n_neighbors': [3,5,7,9,11,13,15,17,19,21,23,25,27,29,31]}
grid_optimization(knn, parameters, X_train, y_train, X_test)
С целью поиска оптимальной модели с более высокими метриками попробуем использовать другие модели классификации.
Логистическая регрессия
Аналогично обучим модель и посмотрим на результат.
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(penalty = 'l2')
lr.fit(X_train, y_train)
lr_y_prediction = lr.predict(X_test)
print("Train quality:")
quality_report(lr.predict(X_train), y_train)
print("\nTest quality:")
quality_report(lr.predict(X_test), y_test)
Построим матрицу ошибок и Roc-Auc.
cm = confusion_matrix(y_test, lr_y_prediction)
cm
roc_auc(lr, X_test, y_test )
По результатам метрик понимаем, что логистическая регрессия не самая оптимальная модель.
Визуализируем влияние признаков на предсказательную роль модели.
featureImportance = pd.DataFrame({'feature': X.columns, 'importance': lr.coef_[0]})
featureImportance
featureImportance.set_index('feature', inplace=True)
featureImportance.sort_values(['importance'], ascending=False, inplace=True)
featureImportance['importance'].plot.bar()
Подбираем гиперпараметры для логистической регрессии.
log_reg = LogisticRegression()
parameters = {'penalty': ['l1', 'l2', 'elasticnet', None], 'C': [0.001, 0.01, 0.1, 1] }
grid_optimization(log_reg, parameters, X_train, y_train, X_test)
Продолжаем поиск оптимальной модели. Остальной алгоритм работы проводим аналогично: обучаем модель, смотрим метрики, подбираем гиперпараметры.
Дерево решений
На примере данной модели мне было интересно попробовать разные способы визуализации деревьев. Желание использовать разные варианты пришло не сразу: дело в том, что по мере построения дерева, у меня что-то ломалось. Поэтому я решила, что не будет лишним иметь в своем учебном арсенале разные варианты визуализации деревьев.
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(max_depth=3, min_samples_leaf=10, min_samples_split=2, criterion='gini')
dt.fit(X_train, y_train)
dt_y_pred = dt.predict(X_test)
Код ниже построит дерево решений в текстовом формате.
from sklearn.tree import export_text
tree_rules = export_text(dt, feature_names = list(X.columns))
print(tree_rules)
Теперь посмотрим это же дерево в графическом виде. Попробуем это сделать несколькими способами.
Вариант №1
fig = plt.figure(figsize=(20,6), dpi=80, facecolor='w', edgecolor='k')
_ = tree.plot_tree(dt, feature_names=X.columns, class_names=True, filled=True)
Вариант №2
import os
os.environ["PATH"] += os.pathsep + "C:/Program Files/Graphviz/bin/"
import graphviz
# DOT data
dot_data = tree.export_graphviz(dt, out_file=None,
feature_names=list(X.columns),
class_names=True,
filled=True)
# Draw graph
graph = graphviz.Source(dot_data, format="png")
graph
Код ниже позволяет сохранить построенное дерево в виде картинки.
graph.render("decision_tree_graphivz")
Вариант №3
from sklearn.tree import export_graphviz
import os, graphviz,pydotplus
os.environ["PATH"] += os.pathsep + 'C:/Program Files (x86)/Graphviz2.38/bin/'
def plot_tree(model, cols, fname='temp_tree.png'):
dot_data = export_graphviz(model, filled=True, rounded=True, feature_names=cols, out_file=None)
pydot_graph = pydotplus.graph_from_dot_data(dot_data)
pydot_graph.write_png(fname)
img = plt.imread(fname)
plt.imshow(img)
plt.figure(figsize=(25, 25))
plt.axis('off')
plot_tree(dt, list(X.columns))
Во всех трех случаях мы получим вот такое дерево:
Т.к. на примере этой модели я ставила перед собой цель научиться по-разному визуализировать деревья, то решила попробовать еще один способ. Получился вот такой довольно наглядный результат.
from dtreeviz import *
from dtreeviz.models.shadow_decision_tree import ShadowDecTree
shadow_tree = ShadowDecTree.get_shadow_tree(dt,X_train, y_train, feature_names = list(X.columns), target_name='TravelInsurance')
model = DTreeVizAPI(shadow_tree)
model.view(scale=2.0)
Вернемся к метрикам и подбору гиперпараметров.
print("Train quality:")
quality_report(dt.predict(X_train), y_train)
print("\nTest quality:")
quality_report(dt.predict(X_test), y_test)
cm = confusion_matrix(y_test, dt_y_pred)
cm
roc_auc(dt, X_test, y_test )
Подбираем гиперпараметры.
dt = DecisionTreeClassifier()
parameters = {'max_depth':[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, None],
'min_samples_split' : [2,5,10,20],
'min_samples_leaf':[1, 5, 10,50],
'criterion' :['gini', 'entropy', 'log_loss']
}
grid_optimization(dt, parameters, X_train, y_train, X_test)
Посмотрим какие признаки оказали наибольшее влияние на построение дерева.
dt.feature_importances_
pd.DataFrame(dt.feature_importances_, index = list(X.columns), columns = ['feature importance']).sort_values('feature importance', ascending = False)
Bagging
Попробуем обучить ансамбль деревьев.
from sklearn.ensemble import BaggingClassifier
bagging_model = BaggingClassifier(base_estimator=DecisionTreeClassifier(max_depth=3), n_jobs=-1, n_estimators=350)
bagging_model.fit(X_train, y_train)
bagging_model_y_pred = bagging_model.predict(X_test)
print("Train quality:")
quality_report(bagging_model.predict(X_train), y_train)
print("\nTest quality:")
quality_report(bagging_model.predict(X_test), y_test)
cm = confusion_matrix(y_test, bagging_model_y_pred)
cm
roc_auc(bagging_model, X_test, y_test )
В результате применения Bagging удалось незначительно улучшить метрики (f1_score
, roc_auc_score
).
Random Forest
Модель Random Forest продемонстрировала результаты, сопоставимые с Bagging.
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_estimators=50, n_jobs=-1, max_depth = 3, max_features = None, oob_score=True,
min_samples_split = 3, min_samples_leaf = 5, bootstrap = True, criterion = 'log_loss')
rf.fit(X_train, y_train)
rf_y_pred = rf.predict(X_test)
print("Train quality:")
quality_report(rf.predict(X_train), y_train)
print("\nTest quality:")
quality_report(rf.predict(X_test), y_test)
roc_auc(rf, X_test, y_test )
rf = RandomForestClassifier()
parameters = {'n_estimators': [5,10,50, 100, 200, 300],
'max_features' : [None, 1, 3, 5, 7],
'max_depth':[None, 1, 2, 3, 4, 5, 6, 7],
'criterion': ['gini', 'entropy', 'log_loss'],
'bootstrap': [True, False]
}
grid_optimization(rf, parameters, X_train, y_train, X_test)
Попробуем использовать бустинги. Начнем с градиентного бустинга. Далее посмотрим CatBoostClassifier
и LGBMClassifier
.
Градиентный бустинг
Градиентный бустинг продемонстрировал чуть более высокий показатель f1_score
, в то же время уменьшился roc_auc_score
.
from sklearn.ensemble import GradientBoostingClassifier
gb = GradientBoostingClassifier(n_estimators=10, learning_rate=1.0, max_depth=3, random_state=0, max_features = None)
gb.fit(X_train, y_train)
gb_y_pred = gb.predict(X_test)
print("Train quality:")
quality_report(gb.predict(X_train), y_train)
print("\nTest quality:")
quality_report(gb.predict(X_test), y_test)
roc_auc(gb, X_test, y_test )
gb = GradientBoostingClassifier()
parameters = {'max_depth':[1, 3, 5, 7, 10, None],
'n_estimators': [5,10,50, 100, 500]
}
grid_optimization(gb, parameters, X_train, y_train, X_test)
CatBoost
Модель CatBoost позволяет улучшить roc_auc_score
по сравнение с градиентным бустингом.
from catboost import CatBoostClassifier
cb = CatBoostClassifier(iterations=10, learning_rate=0.5)
cb.fit(X_train, y_train)
cb_y_pred = cb.predict(X_test)
print("Train quality:")
quality_report(cb.predict(X_train), y_train)
print("\nTest quality:")
quality_report(cb.predict(X_test), y_test)
roc_auc(cb, X_test, y_test )
cb = CatBoostClassifier()
parameters = {'iterations': [5,10,50, 70, 100],
'learning_rate':[0.01, 0.1, 0.15, 0.3, 0.5],
}
grid_optimization(cb, parameters, X_train, y_train, X_test)
LightGBM
Применение LightGBM обеспечило самый высокий показатель roc_auc_score
без ухудшения показателя f1_score
. Конечно, результаты далеки от идеала, но тем не менее хотя бы небольшое улучшение метрики.
from lightgbm import LGBMClassifier
lgbm = LGBMClassifier(n_estimators=10)
lgbm.fit(X_train, y_train)
lgbm_y_pred = lgbm.predict(X_test)
print("Train quality:")
quality_report(lgbm.predict(X_train), y_train)
print("\nTest quality:")
quality_report(lgbm.predict(X_test), y_test)
roc_auc(lgbm, X_test, y_test )
lgbm = LGBMClassifier()
parameters = {'n_estimators': [5,10,50, 100, 200, 300]}
grid_optimization(lgbm, parameters, X_train, y_train, X_test)
SVC
Метод опорных векторов показал ухудшение f1_score
.
from sklearn.svm import SVC
svm = SVC(kernel='rbf', degree=1, gamma='scale', C=1.0, probability=True)
svm.fit(X_train, y_train)
svm_y_pred = svm.predict(X_test)
print("Train quality:")
quality_report(svm.predict(X_train), y_train)
print("\nTest quality:")
quality_report(svm.predict(X_test), y_test)
roc_auc(svm, X_test, y_test )
svm = SVC()
parameters = {'C':[1.0, 10.0, 20.0, None,],
'kernel': ['linear', 'poly', 'rbf', 'sigmoid', 'rbf'],
'gamma' : ['scale', 'auto'],
'degree' : [1, 3]
}
grid_optimization(svm, parameters, X_train, y_train, X_test)
Naive bayes
Naive bayes понизил результат метрики roc_auc_score
.
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB(priors = None, var_smoothing = 1e-09)
gnb.fit(X_train, y_train)
gnb_y_pred = gnb.predict(X_test)
print("Train quality:")
quality_report(gnb.predict(X_train), y_train)
print("\nTest quality:")
quality_report(gnb.predict(X_test), y_test)
roc_auc(gnb, X_test, y_test )
gnb = GaussianNB()
parameters = { 'priors': [None, [0.1,]* len(['TravelInsurance']),],
'var_smoothing': [1e-9, 1e-6, 1e-12],
}
grid_optimization(gnb, parameters, X_train, y_train, X_test)
Теперь сравниваем все модели между собой. Будем ориентироваться на наиболее показательные метрики f1_score
и roc_auc_score
.
compare_models = pd.DataFrame({
'Model Name': ['KNN', 'LR', 'DecisionTree', 'Bagging', 'RandomForest',
'GBoost', 'CatBoost', 'LightGBM', 'SVM', 'Naive Bayes'],
'True Positive': [confusion_matrix(y_test, knn_predictions_y).ravel()[0],
confusion_matrix(y_test, lr_y_prediction).ravel()[0],
confusion_matrix(y_test, dt_y_pred).ravel()[0],
confusion_matrix(y_test, bagging_model_y_pred).ravel()[0],
confusion_matrix(y_test, rf_y_pred).ravel()[0],
confusion_matrix(y_test, gb_y_pred).ravel()[0],
confusion_matrix(y_test, cb_y_pred).ravel()[0],
confusion_matrix(y_test, lgbm_y_pred).ravel()[0],
confusion_matrix(y_test, svm_y_pred).ravel()[0],
confusion_matrix(y_test, gnb_y_pred).ravel()[0]],
'True Negative': [confusion_matrix(y_test, knn_predictions_y).ravel()[1],
confusion_matrix(y_test, lr_y_prediction).ravel()[1],
confusion_matrix(y_test, dt_y_pred).ravel()[1],
confusion_matrix(y_test, bagging_model_y_pred).ravel()[1],
confusion_matrix(y_test, rf_y_pred).ravel()[1],
confusion_matrix(y_test, gb_y_pred).ravel()[1],
confusion_matrix(y_test, cb_y_pred).ravel()[1],
confusion_matrix(y_test, lgbm_y_pred).ravel()[1],
confusion_matrix(y_test, svm_y_pred).ravel()[1],
confusion_matrix(y_test, gnb_y_pred).ravel()[1]],
'False Positive': [confusion_matrix(y_test, knn_predictions_y).ravel()[2],
confusion_matrix(y_test, lr_y_prediction).ravel()[2],
confusion_matrix(y_test, dt_y_pred).ravel()[2],
confusion_matrix(y_test, bagging_model_y_pred).ravel()[2],
confusion_matrix(y_test, rf_y_pred).ravel()[2],
confusion_matrix(y_test, gb_y_pred).ravel()[2],
confusion_matrix(y_test, cb_y_pred).ravel()[2],
confusion_matrix(y_test, lgbm_y_pred).ravel()[2],
confusion_matrix(y_test, svm_y_pred).ravel()[2],
confusion_matrix(y_test, gnb_y_pred).ravel()[2]],
'False Negative': [confusion_matrix(y_test, knn_predictions_y).ravel()[3],
confusion_matrix(y_test, lr_y_prediction).ravel()[3],
confusion_matrix(y_test, dt_y_pred).ravel()[3],
confusion_matrix(y_test, bagging_model_y_pred).ravel()[3],
confusion_matrix(y_test, rf_y_pred).ravel()[3],
confusion_matrix(y_test, gb_y_pred).ravel()[3],
confusion_matrix(y_test, cb_y_pred).ravel()[3],
confusion_matrix(y_test, lgbm_y_pred).ravel()[3],
confusion_matrix(y_test, svm_y_pred).ravel()[3],
confusion_matrix(y_test, gnb_y_pred).ravel()[3]],
'Accuracy': [accuracy_score(y_test, knn_predictions_y),
accuracy_score(y_test, lr_y_prediction),
accuracy_score(y_test, dt_y_pred),
accuracy_score(y_test, bagging_model_y_pred),
accuracy_score(y_test, rf_y_pred),
accuracy_score(y_test, gb_y_pred),
accuracy_score(y_test, cb_y_pred),
accuracy_score(y_test, lgbm_y_pred),
accuracy_score(y_test, svm_y_pred),
accuracy_score(y_test, gnb_y_pred)],
'Precision' : [precision_score(y_test, knn_predictions_y),
precision_score(y_test, lr_y_prediction),
precision_score(y_test, dt_y_pred),
precision_score(y_test, bagging_model_y_pred),
precision_score(y_test, rf_y_pred),
precision_score(y_test, gb_y_pred),
precision_score(y_test, cb_y_pred),
precision_score(y_test, lgbm_y_pred),
precision_score(y_test, svm_y_pred),
precision_score(y_test, gnb_y_pred)],
'Recall' : [recall_score(y_test, knn_predictions_y),
recall_score(y_test, lr_y_prediction),
recall_score(y_test, dt_y_pred),
recall_score(y_test, bagging_model_y_pred),
recall_score(y_test, rf_y_pred),
recall_score(y_test, gb_y_pred),
recall_score(y_test, cb_y_pred),
recall_score(y_test, lgbm_y_pred),
recall_score(y_test, svm_y_pred),
recall_score(y_test, gnb_y_pred)],
'F1 Score' : [f1_score(y_test, knn_predictions_y),
f1_score(y_test, lr_y_prediction),
f1_score(y_test, dt_y_pred),
f1_score(y_test, bagging_model_y_pred),
f1_score(y_test, rf_y_pred),
f1_score(y_test, gb_y_pred),
f1_score(y_test, cb_y_pred),
f1_score(y_test, lgbm_y_pred),
f1_score(y_test, svm_y_pred),
f1_score(y_test, gnb_y_pred)],
'AUC Score' : [roc_auc_score(y_test, knn.predict_proba(X_test)[:,1]),
roc_auc_score(y_test, lr.predict_proba(X_test)[:,1]),
roc_auc_score(y_test, dt.predict_proba(X_test)[:,1]),
roc_auc_score(y_test, bagging_model.predict_proba(X_test)[:,1]),
roc_auc_score(y_test, rf.predict_proba(X_test)[:,1]),
roc_auc_score(y_test, gb.predict_proba(X_test)[:,1]),
roc_auc_score(y_test, cb.predict_proba(X_test)[:,1]),
roc_auc_score(y_test, lgbm.predict_proba(X_test)[:,1]),
roc_auc_score(y_test, svm.predict_proba(X_test)[:,1]),
roc_auc_score(y_test, gnb.predict_proba(X_test)[:,1])],
})
compare_models
По результатам обучения 10 моделей ни одна из них не приблизила метрики roc_auc_score
и f1_score
хотя бы к 0,90–0,95. Это связано с низкой корреляцией признаков при небольшом размере датасета. Самый высокий показатель roc_auc_score
получаем с помощью моделей: KNN, SVM. Самый высокий f1_score
, применяя модели: GBoost, Decision Tree, Bagging, Random Forest, Cat Boost, LightGBM. Для дальнейшей работы я выбрала модель LightGBM, т. к. она демонстрирует один из наиболее высоких результатов по обоим метрикам.
Загрузим второй датасет и сделаем прогноз покупки страховки
Датасет №2 имеет размер 1303 строки, 8 столбцов. Не содержит целевого признака. 4 категориальных признака. Нет пропусков. 483 дубликата. После удаления дубликатов датасет содержит 820 строк.
data_new = pd.read_csv('Travel_Company_New_Clients.csv', sep = ';')
data_new.head()
data_new.shape
data_new.info()
data_new.duplicated().sum()
data_new.drop_duplicates(inplace = True)
data_new = data_new.reset_index(drop = True)
data_new.info()
Перекодируем категориальные признаки тем же способом, который применяли в первом датасете.
data_new['GraduateOrNot'].unique()
data_new['GraduateOrNot'] = data_new['GraduateOrNot'].apply(lambda x: 1 if x=='Yes' else 0)
data_new['FrequentFlyer'].unique()
data_new['FrequentFlyer'] = data_new['FrequentFlyer'].apply(lambda x: 1 if x=='Yes' else 0)
data_new['EverTravelledAbroad'].unique()
data_new['EverTravelledAbroad'] = data_new['EverTravelledAbroad'].apply(lambda x: 1 if x=='Yes' else 0)
data_new['Employment Type'].unique()
print(data_new['Employment Type']. value_counts ()['Government Sector'])
print(data_new['Employment Type']. value_counts ()['Private Sector/Self Employed'])
enc = OneHotEncoder()
enc.fit_transform(data_new[['Employment Type']])
data_new = data_new.drop(['Employment Type'], axis = 1).join(enc.fit_transform(data_new[['Employment Type']], axis = 0))
data_new.head()
Аналогично предыдущему датасету, в новом датасете признаки низко скоррелированы между собой.
data_new.describe()
data_new.corr()
plt.subplots(figsize=(10,7))
sns.heatmap(data_new.corr(), cbar=True, annot=True, square=True, fmt='.2f', annot_kws={'size': 10},\
cmap=sns.color_palette("coolwarm", 10000), vmin=-1, center=0)
plt.show()
Для того, чтобы понять можем ли мы в ранее выбранную модель LightGBM подавать новый датасет, нужно определить на сколько новый датасет похож на тот, на котором обучалась модель. Можем сравнивать попарно признаки между собой, например, расположив рядом два графика и визуально сравнить их сходство. Либо можем применить метод compare (при этом, если мы используем дефолтные параметры keep_equal, keep_shape, то метод покажет только различия).
data_1.compare(data_2, result_names=('data_old', 'data_new'), align_axis=0)
Сравнив статистические показатели по каждому признаку в старом и новом датасете, видим, что датасеты похожи, значит корректно будет в ранее обученную модель подавать новый датасет. Загружаем новый датасет в модель LightGBM в качестве новой тестовой выборки и получаем предсказание целевого признака — покупки страховки.
X_new_test = scaler.transform(data_new)
X_train.shape, X_new_test.shape, y_train.shape
lgbm_predictions_new_y = lgbm.predict(X_new_test)
data_new['predicted_TravelInsurance'] = lgbm_predictions_new_y
Проверяем, что модель действительно предсказала целевое значение — покупку страховки клиентами туроператора. Смотрим первые пять строк датасета и видим, что появилась колонка Predicted_TravelInsurance
cо значениями.
data_new.head()
Модель спрогнозировала, что 25% клиентов (205 человек) купят страховку, остальные 75% клиентов (615 человек) не будут покупать страховку.
data_new['Predicted_TravelInsurance'].value_counts(normalize=True)*100
© Habrahabr.ru