Логистическая регрессия на Python

от автора

Логистическая регрессия — это алгоритм классификации в машинном обучении для прогнозирования вероятности категориально зависимой переменной. В логистической регрессии зависимые переменные — это двоичные (бинарные) переменные, содержащие 1 (да, успех, и так далее) или 0 — нет, неудача, и так далее. Другими словами, логистическая регрессия прогнозирует P(Y=1) как функцию от X. Подробный и ясный пример — к старту нашего флагманского курса по Data Science.

Допущения логистической регрессии:

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

 Имея в виду эти допущения, посмотрим на набор данных.

Данные

Набор данных из реозитория UCI Machine Learning связан с целевыми маркетинговыми компаниями (телефонными звонками) Португальского банка. Задача классификации — спрогнозировать (0/1), то есть откроет ли клиент срочный вклад (переменная y). Загрузить набор можно отсюда.

import pandas as pd import numpy as np from sklearn import preprocessing import matplotlib.pyplot as plt  plt.rc("font", size=14) from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split import seaborn as sns sns.set(style="white") sns.set(style="whitegrid", color_codes=True)

Набор посвящён клиентам банка и содержит 41188 записей с 21 полем:

Входные переменные

  1. age: возраст (числовая);
  2. job: работа (категориальная: «admin», «blue-collar», «entrepreneur», «housemaid», «management», «retired», «self-employed», «services», «student», «technician», «unemployed», «unknown»);
  3. marital: семейное положение (категориальная: «divorced», «married», «single», «unknown»);
  4. education: образование (категориальная: «basic.4y», «basic.6y», «basic.9y», «high.school», «illiterate», «professional.course», «university.degree», «unknown»);
  5. default: есть невыплаченный кредит? (категориальная: «no», «yes», «unknown»);
  6. housing: есть жилищный кредит? (категориальная: «no», «yes», «unknown»);
  7. loan: есть кредит на личные нужды? (категориальная: «no», «yes», «unknown»);
  8. contact: мобильный или стационарный телефон? (категориальная: «cellular», «telephone»);
  9. month: месяц последнего звонка (категориальная: «jan», «feb», «mar», …, «nov», «dec»);
  10. day_of_week: день недели последнего звонка (категориальная: «mon», «tue», «wed», «thu», «fri»);
  11. duration: продолжительность разговора в секундах (числовая). Важное примечание: этот атрибут сильно влияет на конечный результат (например, если duration=0, то y=’no’); Продолжительность не известна до выполнения вызова, кроме того, после окончания вызова y, очевидно, известна. Таким образом, эти входные данные должны включаться только для сравнения и отбрасываться, когда нужна реалистичная прогностическая модель;
  12. campaign: число контактов в ходе этой кампании и для этого клиента (числовая), включая последний контакт);
  13. pdays: количество дней, прошедших с последнего контакта с клиентом, с предыдущей кампании (числовая); 999 означает, что ранее контакта не было);
  14. previous: количество контактов до этой кампании для данного клиента (числовая)
  15. poutcome: исход предыдущей кампании (категориальная: «failure», «nonexistent», «success»)
  16. emp.var.rate: коэффициент вариации занятости — (числовая);
  17. cons.price.idx: индекс потребительских цен — (числовая);
  18. cons.conf.idx: индекс доверия потребителей — (числовая);
  19. euribor3m: ставка EURIBOR за 3 месяца — (числовая);
  20. nr.employed: количество работников — (числовая).

Наша цель — прогноз переменной Y, то есть того, есть ли у клиента срочный вклад? (0 — нет, 1 — да).

У поля об образовании (education) много категорий. Чтобы сделать модель лучше, количество этих категорий нужно сократить:

Для этого сгруппируем «basic.4y», «basic.9y» и «basic.6y», назовём их «Basic»:

data['education']=np.where(data['education'] =='basic.9y', 'Basic', data['education']) data['education']=np.where(data['education'] =='basic.6y', 'Basic', data['education']) data['education']=np.where(data['education'] =='basic.4y', 'Basic', data['education'])

Вот поля после группировки:

Исследование данных

count_no_sub = len(data[data['y']==0]) count_sub = len(data[data['y']==1]) pct_of_no_sub = count_no_sub/(count_no_sub+count_sub) print("percentage of no subscription is", pct_of_no_sub*100) pct_of_sub = count_sub/(count_no_sub+count_sub) print("percentage of subscription", pct_of_sub*100)

  • без вклада — 88,73458288821988;
  • с вкладом — 11.265417111780131.

Классы наших данных не сбалансированы, а соотношение клиентов без вклада и с вкладом составляет 89 к 11. До балансировки классов проведём ещё несколько исследований:

Вот новые наблюдения:

  • Средний возраст клиентов, открывших вклад, выше среднего возраста тех, кто этого не сделал.
  • Количество дней с последнего контакта с клиентом по понятным причинам меньше у клиентов, открывших депозит. А чем она меньше, тем лучше запомнился последний звонок, а значит, выше шанс открытия счёта.
  • Удивительно, но кампания, то есть количество звонков за одну кампанию, меньше у клиентов, которые открыли депозит.

Чтобы чётче представлять данные, рассчитаем категориальное среднее других категориальных переменных, таких как образование и семейное положение.

Визуализация

%matplotlib inline pd.crosstab(data.job,data.y).plot(kind='bar') plt.title('Purchase Frequency for Job Title') plt.xlabel('Job') plt.ylabel('Frequency of Purchase') plt.savefig('purchase_fre_job')

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

table=pd.crosstab(data.marital,data.y) table.div(table.sum(1).astype(float), axis=0).plot(kind='bar', stacked=True) plt.title('Stacked Bar Chart of Marital Status vs Purchase') plt.xlabel('Marital Status') plt.ylabel('Proportion of Customers') plt.savefig('mariral_vs_pur_stack')

Семейное положение сильным фактором прогнозирования не выглядит:

table=pd.crosstab(data.education,data.y) table.div(table.sum(1).astype(float), axis=0).plot(kind='bar', stacked=True) plt.title('Stacked Bar Chart of Education vs Purchase') plt.xlabel('Education') plt.ylabel('Proportion of Customers') plt.savefig('edu_vs_pur_stack')

Также хорошим фактором прогнозирования выглядит категория образования:

pd.crosstab(data.day_of_week,data.y).plot(kind='bar') plt.title('Purchase Frequency for Day of Week') plt.xlabel('Day of Week') plt.ylabel('Frequency of Purchase') plt.savefig('pur_dayofweek_bar')

А день недели — нет:

pd.crosstab(data.day_of_week,data.y).plot(kind='bar') plt.title('Purchase Frequency for Day of Week') plt.xlabel('Day of Week') plt.ylabel('Frequency of Purchase') plt.savefig('pur_dayofweek_bar')

Хорошим фактором прогнозирования может быть месяц:

data.age.hist() plt.title('Histogram of Age') plt.xlabel('Age') plt.ylabel('Frequency') plt.savefig('hist_age')

Большинству клиентов банка в этом наборе данных от 30 до 40 лет.

pd.crosstab(data.poutcome,data.y).plot(kind='bar') plt.title('Purchase Frequency for Poutcome') plt.xlabel('Poutcome') plt.ylabel('Frequency of Purchase') plt.savefig('pur_fre_pout_bar')

Наконец, хорошим фактором прогнозирования выглядит Poutcome.

Переменные-заглушки

Эти переменные содержат только значения 0 и 1.

cat_vars=['job','marital','education','default','housing','loan','contact','month','day_of_week','poutcome'] for var in cat_vars:     cat_list='var'+'_'+var     cat_list = pd.get_dummies(data[var], prefix=var)     data1=data.join(cat_list)     data=data1 cat_vars=['job','marital','education','default','housing','loan','contact','month','day_of_week','poutcome'] data_vars=data.columns.values.tolist() to_keep=[i for i in data_vars if i not in cat_vars]

И последние поля:

data_final=data[to_keep] data_final.columns.values

Обогащение синтетическими данными через SMOTE

Я увеличу выборку данных о клиентах без вклада при помощи алгоритма SMOTE (Synthetic Minority Oversampling Technique). На высоком уровне он работает так:

  1. Генерирует синтетические выборки из минорного класса, а не создаёт копии.
  2. Случайно выбирает одного из k ближайших соседей и использует его, чтобы создать подобное, но случайно подстроенное наблюдение.

Реализуем этот алгоритм на Python:

X = data_final.loc[:, data_final.columns != 'y'] y = data_final.loc[:, data_final.columns == 'y'] from imblearn.over_sampling import SMOTE os = SMOTE(random_state=0) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0) columns = X_train.columns os_data_X,os_data_y=os.fit_sample(X_train, y_train) os_data_X = pd.DataFrame(data=os_data_X,columns=columns ) os_data_y= pd.DataFrame(data=os_data_y,columns=['y']) # we can Check the numbers of our data print("length of oversampled data is ",len(os_data_X)) print("Number of no subscription in oversampled data",len(os_data_y[os_data_y['y']==0])) print("Number of subscription",len(os_data_y[os_data_y['y']==1])) print("Proportion of no subscription data in oversampled data is ",len(os_data_y[os_data_y['y']==0])/len(os_data_X)) print("Proportion of subscription data in oversampled data is ",len(os_data_y[os_data_y['y']==1])/len(os_data_X))

Теперь данные сбалансированы идеально! Вы могли заметить, что я увеличила выборки только тренировочных данных; никакая информация из тестовых данных не используется для генерации синтетических наблюдений, то есть никакая информация тестового набора не утечёт в обучающие данные.

Рекурсивное устранение признаков

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

data_final_vars=data_final.columns.values.tolist() y=['y'] X=[i for i in data_final_vars if i not in y] from sklearn.feature_selection import RFE from sklearn.linear_model import LogisticRegression logreg = LogisticRegression() rfe = RFE(logreg, 20) rfe = rfe.fit(os_data_X, os_data_y.values.ravel()) print(rfe.support_) print(rfe.ranking_)

 Метод помог выбрать следующие признаки: «euribor3m», «job_blue-collar», «job_housemaid», «marital_unknown», «education_illiterate», «default_no», «default_unknown», «contact_cellular», «contact_telephone», «month_apr», «month_aug», «month_dec», «month_jul», «month_jun», «month_mar», «month_may», «month_nov», «month_oct», «poutcome_failure», «poutcome_success».

cols=['euribor3m', 'job_blue-collar', 'job_housemaid', 'marital_unknown', 'education_illiterate', 'default_no', 'default_unknown',        'contact_cellular', 'contact_telephone', 'month_apr', 'month_aug', 'month_dec', 'month_jul', 'month_jun', 'month_mar',        'month_may', 'month_nov', 'month_oct', "poutcome_failure", "poutcome_success"]  X=os_data_X[cols] y=os_data_y['y']

Реализация модели

import statsmodels.api as sm logit_model=sm.Logit(y,X) result=logit_model.fit() print(result.summary2())

p-значение большинства переменных меньше 0,05, кроме четырёх, которые мы удалим:

cols=['euribor3m', 'job_blue-collar', 'job_housemaid', 'marital_unknown', 'education_illiterate',        'month_apr', 'month_aug', 'month_dec', 'month_jul', 'month_jun', 'month_mar',        'month_may', 'month_nov', 'month_oct', "poutcome_failure", "poutcome_success"]  X=os_data_X[cols] y=os_data_y['y'] logit_model=sm.Logit(y,X) result=logit_model.fit() print(result.summary2())

Обучение модели логистической регрессии

from sklearn.linear_model import LogisticRegression from sklearn import metrics X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0) logreg = LogisticRegression() logreg.fit(X_train, y_train)

Прогноз на тестовом наборе и расчёт точности:

y_pred = logreg.predict(X_test) print('Accuracy of logistic regression classifier on test set: {:.2f}'.format(logreg.score(X_test, y_test)))

Точность классификатора логистической регрессии на тестовом наборе: 0,74

Матрица путаницы

from sklearn.metrics import confusion_matrix confusion_matrix = confusion_matrix(y_test, y_pred) print(confusion_matrix)

[[6124 1542] [2505 5170]]

У нас 6124+5170 верных и 2505+1542 неверных прогнозов.

Расчёт точности, полноты, F-меры и носителя

Цитата от Scikit Learn:

Точность — это отношение tp/(tp + fp), где tp — число истинных и ложных срабатываний, а интуитивно — это способность модели не отмечать истинные примеры как ложные.

Полнота — это отношение tp/(tp + fn), то есть числа истинных и ложных срабатываний, а интуитивно — способность модели найти все истинные примеры.

Оценку F-beta можно понимать как взвешенное гармоническое среднее точности и полноты. Лучшее значение оценки — 1, худшее — 0. Эта оценка взвешивает полноту тщательнее точности, умножая её на коэффициент бета. Бета, равная 1.0, означает одинаковую важность полноты и точности.

Носитель — это количество вхождений каждого класса в y_test.

from sklearn.metrics import classification_report print(classification_report(y_test, y_pred))

Интерпретация:

  • 74% рекламируемых срочных депозитов из набора данных понравились клиентам.
  • 74% срочных депозитов, которые предпочли клиенты, были прорекламированы.

ROC-кривая

from sklearn.metrics import roc_auc_score from sklearn.metrics import roc_curve logit_roc_auc = roc_auc_score(y_test, logreg.predict(X_test)) fpr, tpr, thresholds = roc_curve(y_test, logreg.predict_proba(X_test)[:,1]) plt.figure() plt.plot(fpr, tpr, label='Logistic Regression (area = %0.2f)' % logit_roc_auc) plt.plot([0, 1], [0, 1],'r--') plt.xlim([0.0, 1.0]) plt.ylim([0.0, 1.05]) plt.xlabel('False Positive Rate') plt.ylabel('True Positive Rate') plt.title('Receiver operating characteristic') plt.legend(loc="lower right") plt.savefig('Log_ROC') plt.show()

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

Интерактивный блокнот для этого поста лежит здесь. Мне будет приятно получить отзыв или вопрос в комментариях.

Ссылка: Learning Predictive Analytics with Python

Научим вас аккуратно работать с данными, чтобы вы прокачали карьеру и стали востребованным IT-специалистом. Если вы не найдёте работу, мы просто вернём деньги (возврат — акция в рамках «Чёрной пятницы»).


ссылка на оригинал статьи https://habr.com/ru/company/skillfactory/blog/701530/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *