tinkoff
In [1]:
# задача с конкурса Tinkoff: https://boosters.pro/champ_3
#Датасет содержит данные о кредитах на покупку электроники, которые были одобрены Tinkoff.ru. 
# Необходимо предсказать, выберет ли покупатель кредит от Tinkoff.ru
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
In [2]:
# записываем CSV-файл в объект DataFrame
data = pd.read_csv('https://raw.githubusercontent.com/Gewissta/Learning_Pandas_russian_translation/master/Notebooks/Data/credit_train.csv', encoding='cp1251', sep=';')
In [3]:
# смотрим на первые пять строк
data.head()
Out[3]:
client_id gender age marital_status job_position credit_sum credit_month tariff_id score_shk education living_region monthly_income credit_count overdue_credit_count open_account_flg
0 1 M NaN NaN UMN 59998,00 10 1.6 NaN GRD КРАСНОДАРСКИЙ КРАЙ 30000.0 1.0 1.0 0
1 2 F NaN MAR UMN 10889,00 6 1.1 NaN NaN МОСКВА NaN 2.0 0.0 0
2 3 M 32.0 MAR SPC 10728,00 12 1.1 NaN NaN ОБЛ САРАТОВСКАЯ NaN 5.0 0.0 0
3 4 F 27.0 NaN SPC 12009,09 12 1.1 NaN NaN ОБЛ ВОЛГОГРАДСКАЯ NaN 2.0 0.0 0
4 5 M 45.0 NaN SPC NaN 10 1.1 0,421385 SCH ЧЕЛЯБИНСКАЯ ОБЛАСТЬ NaN 1.0 0.0 0
In [4]:
#проверяем типы данных и заполненность столбцов
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 170746 entries, 0 to 170745
Data columns (total 15 columns):
client_id               170746 non-null int64
gender                  170746 non-null object
age                     170743 non-null float64
marital_status          170743 non-null object
job_position            170746 non-null object
credit_sum              170744 non-null object
credit_month            170746 non-null int64
tariff_id               170746 non-null float64
score_shk               170739 non-null object
education               170741 non-null object
living_region           170554 non-null object
monthly_income          170741 non-null float64
credit_count            161516 non-null float64
overdue_credit_count    161516 non-null float64
open_account_flg        170746 non-null int64
dtypes: float64(5), int64(3), object(7)
memory usage: 19.5+ MB
In [5]:
#удаляем столбец с номером клиента (так как он незначимый) 
# и с регионом проживания (так как он нуждается в серьезной предобработке)
data.drop(['client_id', 'living_region'], axis=1, inplace=True)
In [6]:
# анализируем зависимую переменную: какие значения она принимает и сколько раз
data['open_account_flg'].value_counts(dropna=False)
Out[6]:
0    140690
1     30056
Name: open_account_flg, dtype: int64
In [7]:
# считаем, какая точность (доля правильных ответов) была бы у модели, если всем подряд пресказывать, что кредит они не выберут
d=140690/(140690+30056)
d
Out[7]:
0.823972450306303
In [8]:
# анализируем столбец marital_status, смотрим, какое значение в нем является самым частым 
data['marital_status'].describe()
Out[8]:
count     170743
unique         5
top          MAR
freq       93954
Name: marital_status, dtype: object
In [9]:
# анализируем столбец education, смотрим, какое в нем самое частое значение
data['education'].describe()
Out[9]:
count     170741
unique         5
top          SCH
freq       87537
Name: education, dtype: object
In [10]:
# дозаполняем нечисловые столбцы с пропусками самыми часто встречающимися значениями
data['marital_status'].fillna('MAR', inplace=True)
data['education'].fillna('SCH', inplace=True)
In [11]:
# дозаполняем числовые столбцы с пропусками медианными значениями
data['age'].fillna(data['age'].median(), inplace=True)
data['credit_count'].fillna(data['credit_count'].median(), inplace=True)
data['overdue_credit_count'].fillna(data['overdue_credit_count'].median(), inplace=True)
In [12]:
#меняем в столбцах 'credit_sum', 'score_shk'  запятые на точки  и преобразуем их в числовой  формат
for i in ['credit_sum', 'score_shk']:
    data[i] = data[i].str.replace(',', '.').astype('float')
In [13]:
# дозаполняем ставшие теперь числовыми столбцы 'credit_sum', 'score_shk'   медианными значениями
data['score_shk'].fillna(data['score_shk'].median(), inplace=True)
data['monthly_income'].fillna(data['monthly_income'].median(), inplace=True)
data['credit_sum'].fillna(data['credit_sum'].median(), inplace=True)
In [14]:
# смотрим, что получилось
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 170746 entries, 0 to 170745
Data columns (total 13 columns):
gender                  170746 non-null object
age                     170746 non-null float64
marital_status          170746 non-null object
job_position            170746 non-null object
credit_sum              170746 non-null float64
credit_month            170746 non-null int64
tariff_id               170746 non-null float64
score_shk               170746 non-null float64
education               170746 non-null object
monthly_income          170746 non-null float64
credit_count            170746 non-null float64
overdue_credit_count    170746 non-null float64
open_account_flg        170746 non-null int64
dtypes: float64(7), int64(2), object(4)
memory usage: 16.9+ MB
In [15]:
# кодируем нечисловые столбцы методом дамми-кодирования
data = pd.concat([data, 
                      pd.get_dummies(data['gender'], prefix="gender"),
                      pd.get_dummies(data['job_position'], prefix="job_position"),
                      pd.get_dummies(data['education'], prefix="education"),
                      pd.get_dummies(data['marital_status'], prefix="marital_status")],
                     axis=1)
In [16]:
#удаляем старые нечисловые столбцы, вместо них уже появились новые числовые
data.drop(['gender','job_position','education','marital_status'], axis=1, inplace=True)
In [17]:
# указываем входы и выход модели
y = data['open_account_flg']
X = data.drop(('open_account_flg'), axis=1) 
In [18]:
# формируем из набора данных тестовую и обучающую выборки
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=21)
In [19]:
# обучаем логистическую регрессию
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression()
clf.fit(X_train, y_train);
In [20]:
#считаем точность (долю правильных ответов) на тестовой выборке
clf.score(X_test, y_test)
Out[20]:
0.82383189064458318
In [21]:
# вычисляем метрику точности ROC-AUC (по ней оцениваются результаты конкурса)
y_pred = clf.predict_proba(X_test)[:, 1]
from sklearn.metrics import roc_auc_score
roc_auc_score(y_test, y_pred)
Out[21]:
0.56252172227195185
In [22]:
#для логистической регрессии точность оказалась очень низкой, поэтому строим случайный лес
from sklearn.ensemble import RandomForestClassifier
rfc = RandomForestClassifier(n_estimators = 10,  max_depth=5,  random_state=21)
rfc.fit(X_train, y_train)
rfc.score (X_test, y_test)
#считаем точность (долю правильных ответов) на тестовой выборке
Out[22]:
0.82408958335773608
In [23]:
# вычисляем метрику точности ROC-AUC для случайного леса 
y_pred = rfc.predict_proba(X_test)[:, 1]
from sklearn.metrics import roc_auc_score
roc_auc_score(y_test, y_pred)
Out[23]:
0.70496515285491423
In [24]:
# случайный лес оказался гораздо лучше
# проверяем, нельзя ли улучшить логистическую регрессию, добавив полиномиальные признаки
from sklearn.preprocessing import PolynomialFeatures
pf = PolynomialFeatures(degree=2)
X_train_polynomial = pf.fit_transform(X_train)
X_test_polynomial = pf.fit_transform(X_test)
In [33]:
# обучаем логистическую регрессию с полиномиальными признаками
clf = LogisticRegression()
clf.fit(X_train_polynomial, y_train);
clf.score(X_test_polynomial, y_test)
Out[33]:
0.82374989750857996
In [34]:
y_pred = clf.predict_proba(X_test_polynomial)[:, 1]
roc_auc_score(y_test, y_pred)
Out[34]:
0.57186273596335491
In [ ]:
#сильно лучше не стало
#нужно   добавлять регион и использовать случайный лес
#у победителя ROC-AUC=0.78, у нашего случайного леса 0.70, это где-то 300-я позиция в рейтинге (из 600)