Введение

Добрый день, уважаемые читатели.

Недавно, бродя по просторам глобальной паутины, я наткнулся на турнир, который проводился банком ТКС в начале этого года. Ознакомившись с заданиями, я решил проверить свои навыки в анализе данных на них.

Начать проверку я решил с задачи о скоринге (Задание №3). Для ее решения я, как всегда, использовал Python с аналитическими модулями pandas и scikit-learn.

Описание данных и постановка задачи

Банк запрашивает кредитную историю заявителя в трех крупнейших российских кредитных бюро. Предоставляется выборка клиентов Банка в файле SAMPLE_CUSTOMERS.CSV. Выборка разделена на части «train» и «test». По выборке «train» известно значение целевой переменной bad - наличие “дефолта” (допущение клиентом просрочки 90 и более дней в течение первого года пользования кредитом). В файле SAMPLE_ACCOUNTS.CSV предоставлены данные из ответов кредитных бюро на все запросы по соответствующим клиентам.

Формат данных SAMPLE_CUSTOMERS – информация о счетах человека, передаваемая другими банками в данное бюро.

Описание формата набора данных SAMPLE_ACCOUNTS:

Name Description Type
TCS_CUSTOMER_ID Идентификатор клиента ID
BUREAU_CD Код бюро, из которого получен счет numeric
BKI_REQUEST_DATE Дата, в которую был сделан запрос в бюро date
CURRENCY Валюта договора (ISO буквенный код валюты) string
RELATIONSHIP Тип отношения к договору string
  1 - Физическое лицо  
2 - Дополнительная карта/Авторизованный пользователь
4 - Совместный
5 - Поручитель
9 - Юридическое лицо
OPEN_DATE Дата открытия договора date
FINAL_PMT_DATE Дата финального платежа (плановая) date
TYPE Код типа договора string
  1 – Кредит на автомобиль  
4 – Лизинг
6 – Ипотека
7 – Кредитная карта
9 – Потребительский кредит
10 – Кредит на развитие бизнеса
11 – Кредит на пополнение оборотных средств
12 – Кредит на покупку оборудования
13 – Кредит на строительство недвижимости
14 – Кредит на покупку акций (например, маржинальное кредитование)
99 – Другой
PMT_STRING_84M Дисциплина (своевременность) платежей. Строка составляется из кодов состояний счета на моменты передачи банком данных по счету в бюро, первый символ - состояние на дату PMT_STRING_START, далее последовательно в порядке убывания дат. string
  0 – Новый, оценка невозможна  
X – Нет информации
1 – Оплата без просрочек
A – Просрочка от 1 до 29 дней
2 – Просрочка от 30 до 59 дней
3 – Просрочка от 60 до 89 дней
4 – Просрочка от 90 до 119 дней
5 – Просрочка более 120 дней
7 – Регулярные консолидированные платежи
8 – Погашение по кредиту с использованием залога
9 – Безнадёжный долг/ передано на взыскание/ пропущенный платеж
STATUS Статус договора string
  00 – Активный  
12 – Оплачен за счет обеспечения
13 – Счет закрыт
14 – Передан на обслуживание в другой банк
21 – Спор
52 – Просрочен
61 – Проблемы с возвратом
OUTSTANDING Оставшаяся непогашенная задолженность. Сумма в рублях по курсу ЦБ РФ numeric
NEXT_PMT Размер следующего платежа. Сумма в рублях по курсу ЦБ РФ numeric
INF_CONFIRM_DATE Дата подтверждения информации по счету date
FACT_CLOSE_DATE Дата закрытия счета (фактическая) date
TTL_DELQ_5 Количество просрочек до 5 дней numeric
TTL_DELQ_5_29 Количество просрочек от 5 до 29 дней numeric
TTL_DELQ_30_59 Количество просрочек от 30 до 59 дней numeric
TTL_DELQ_60_89 Количество просрочек от 60 до 89 дней numeric
TTL_DELQ_30 Количество просрочек до 30 дней numeric
TTL_DELQ_90_PLUS Количество просрочек 90+ дней numeric
PMT_FREQ Код частоты платежей string
  1 – Еженедельно  
2 – Раз в две недели
3 – Ежемесячно
A - Раз в 2 месяца
4 – Поквартально
B - Раз в 4 месяца
5 – Раз в полгода
6 - Ежегодно
7 – Другое
CREDIT_LIMIT Кредитный лимит. Сумма в рублях по курсу ЦБ РФ numeric
DELQ_BALANCE Текущая просроченная задолженность. Сумма в рублях по курсу ЦБ РФ numeric
MAX_DELQ_BALANCE Максимальный объем просроченной задолженности. Сумма в рублях по курсу ЦБ РФ numeric
CURRENT_DELQ Текущее количество дней просрочки numeric
PMT_STRING_START Дата начала строки PMT_STRING_84M date
INTEREST_RATE Процентная ставка по кредиту numeric
CURR_BALANCE_AMT Общая выплаченная сумма, включая сумму основного долга, проценты, пени и штрафы. Сумма в рублях по курсу ЦБ РФ numeric

Задача состоит в том, чтобы на выборке «train» необходимо построить модель, определяющую вероятность “дефолта”, и проставить вероятности “дефолта” по клиентам из выборки «test». Для оценки модели будет использоваться характеристика Area Under ROC Curve (также указано в условиях задачи)

Предварительная обработка данных

Для начала загрузим исходные файлы и посмотрим на них:

from pandas import read_csv, DataFrame
from sklearn.metrics import roc_curve
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.cross_validation import train_test_split
from sklearn.naive_bayes import GaussianNB
from sklearn.neighbors import KNeighborsClassifier
from sklearn.decomposition import PCA
import ml_metrics, string, re, pylab as pl
SampleCustomers = read_csv("https://static.tcsbank.ru/documents/olymp/SAMPLE_CUSTOMERS.csv", ';')
SampleAccounts = read_csv("https://static.tcsbank.ru/documents/olymp/SAMPLE_ACCOUNTS.csv",";",decimal =',')
SampleAccounts
<class 'pandas.core.frame.DataFrame'>
Int64Index: 280942 entries, 0 to 280941
Data columns (total 28 columns):
tcs_customer_id     280942  non-null values
bureau_cd           280942  non-null values
bki_request_date    280942  non-null values
inf_confirm_date    280942  non-null values
type                280942  non-null values
status              280942  non-null values
open_date           280942  non-null values
final_pmt_date      262331  non-null values
fact_close_date     138429  non-null values
credit_limit        280942  non-null values
currency            280942  non-null values
outstanding         227030  non-null values
next_pmt            127816  non-null values
curr_balance_amt    102436  non-null values
current_delq        227249  non-null values
ttl_delq_5          280942  non-null values
ttl_delq_5_29       280942  non-null values
ttl_delq_30         280942  non-null values
ttl_delq_30_59      280942  non-null values
ttl_delq_60_89      280942  non-null values
ttl_delq_90_plus    280942  non-null values
pmt_string_start    268870  non-null values
pmt_string_84m      274030  non-null values
delq_balance        280915  non-null values
max_delq_balance    280915  non-null values
interest_rate       280942  non-null values
pmt_freq            280732  non-null values
relationship        280942  non-null values
dtypes: float64(8), int64(16), object(4)
SampleCustomers.head()
tcs_customer_id bad sample_type
0 1 NaN test
1 2 0 train
2 3 1 train
3 4 0 train
4 5 0 train

Теперь давайте объеденим наши наборы данных, для этого добавим данные из SampleCustomers к SampleAccounts: UnionDF = SampleAccounts.merge(SampleCustomers, on = [‘tcs_customer_id’]) Из условий задачи можно предположить, что набор SampleAccounts содержит несколько записей по одному заемщику давайте проверим это:

"%s from %s" % (SampleAccounts.tcs_customer_id.drop_duplicates().count(), SampleAccounts.tcs_customer_id.count())
'50000 from 280942'

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

Для начала нам нужно получим список все уникальных кредитов по заемщику:

SampleAccounts[['tcs_customer_id','open_date','final_pmt_date','credit_limit','currency']].drop_duplicates()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 188376 entries, 0 to 280940
Data columns (total 5 columns):
tcs_customer_id    188376  non-null values
open_date          188376  non-null values
final_pmt_date     175010  non-null values
credit_limit       188376  non-null values
currency           188376  non-null values
dtypes: float64(1), int64(3), object(1)

Следовательно, когда мы получили список кредитов, мы сможем вывести какую-либо общую информацию по каждому элементу списка. Т.е. мы могли бы взять связку из перечисленных выше полей и сделать ее индексом, относительно которого мы бы производили дальнейшие манипуляции, но, к сожалению, тут нас подстерегает один неприятный момент. Он заключается в том, что поле ‘final_pmt_date’ в наборе данных имеет неопределенные значения. Давайте попробуем избавиться от них.

Итак, у нас в наборе есть поле фактическая дата закрытия кредита, следовательно, если она есть, а поле ‘final_pmt_date’ не заполнено, то можно в него записать данное значение. Для остальных же просто запишем 0.

SampleAccounts.final_pmt_date[SampleAccounts.final_pmt_date.isnull()] = SampleAccounts.fact_close_date[SampleAccounts.final_pmt_date.isnull()].astype(float)
SampleAccounts.final_pmt_date.fillna(0, inplace=True)

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

sumtbl = SampleAccounts.pivot_table(['inf_confirm_date'],
                                    ['tcs_customer_id','open_date','final_pmt_date','credit_limit','currency'], 
                                    aggfunc='max')
sumtbl.head(15)
inf_confirm_date
tcs_customer_id open_date final_pmt_date credit_limit currency
1 39261 39629 19421 RUB 39924
39505 39870 30000 RUB 39862
39644 40042 11858 RUB 40043
39876 41701 300000 RUB 40766
39942 40308 19691 RUB 40435
40421 42247 169000 RUB 40756
40428 51386 10000 RUB 40758
40676 41040 28967 RUB 40764
2 40472 40618 7551 RUB 40661
40652 40958 21186 RUB 40661
3 39647 40068 22694 RUB 40069
40604 0 20000 RUB 40624
4 38552 40378 75000 RUB 40479
39493 39797 5000 RUB 39823
39759 40123 6023 RUB 40125

Теперь добавим полученные нами даты к основному набору:

SampleAccounts = SampleAccounts.merge(sumtbl, 'left', 
                                     left_on=['tcs_customer_id','open_date','final_pmt_date','credit_limit','currency'], 
                                     right_index=True,
                                     suffixes=('', '_max'))

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

Код для их преобразования приведене ниже:

# преобразуем pmt_string_84m
vals = list(xrange(10)) + ['A','X']
PMTstr = DataFrame([{'pmt_string_84m_%s' % (str(j)): str(i).count(str(j)) for j in vals} for i in SampleAccounts.pmt_string_84m])
SampleAccounts = SampleAccounts.join(PMTstr).drop(['pmt_string_84m'], axis=1)

# преобразуем pmt_freq
SampleAccounts.pmt_freq.fillna(7, inplace=True)
SampleAccounts.pmt_freq[SampleAccounts.pmt_freq == 0] = 7
vals = list(range(1,8)) + ['A','B']
PMTstr = DataFrame([{'pmt_freq_%s' % (str(j)): str(i).count(str(j)) for j in vals} for i in SampleAccounts.pmt_freq])
SampleAccounts = SampleAccounts.join(PMTstr).drop(['pmt_freq'], axis=1)

# преобразуем type
vals = [1,4,6,7,9,10,11,12,13,14,99]
PMTstr = DataFrame([{'type_%s' % (str(j)): str(i).count(str(j)) for j in vals} for i in SampleAccounts.type])
SampleAccounts = SampleAccounts.join(PMTstr).drop(['type'], axis=1)

# преобразуем status
vals = [0,12, 13, 14, 21, 52,61]
PMTstr = DataFrame([{'status_%s' % (str(j)): str(i).count(str(j)) for j in vals} for i in SampleAccounts.status])
SampleAccounts = SampleAccounts.join(PMTstr).drop(['status'], axis=1)

# преобразуем relationship
vals = [1,2,4,5,9]
PMTstr = DataFrame([{'relationship_%s' % (str(j)): str(i).count(str(j)) for j in vals} for i in SampleAccounts.relationship])
SampleAccounts = SampleAccounts.join(PMTstr).drop(['relationship'], axis=1)

# преобразуем bureau_cd
vals = [1,2,3]
PMTstr = DataFrame([{'bureau_cd_%s' % (str(j)): str(i).count(str(j)) for j in vals} for i in SampleAccounts.bureau_cd])
SampleAccounts = SampleAccounts.join(PMTstr).drop(['bureau_cd'], axis=1)

Следующим шагом, преобразуем поле ‘fact_close_date’, в котором содержится дата последнего фактического платежа, чтобы в нем содержалось только 2 значения:

Данную замену я сделал потому, что изначально поле было заполнено наполовину.

SampleAccounts.fact_close_date[SampleAccounts.fact_close_date.notnull()] = 1
SampleAccounts.fact_close_date.fillna(0, inplace=True)

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

PreFinalDS = SampleAccounts[SampleAccounts.inf_confirm_date == SampleAccounts.inf_confirm_date_max].drop_duplicates()

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

PreFinalDS = PreFinalDS.groupby(['tcs_customer_id','open_date','final_pmt_date','credit_limit','currency']).max().reset_index()

Наши данные почти готовы к началу анализа. Осталось выполнить еще несколько действий:

  1. Убрать не нужные солбцы
  2. Привести все кредитные лимиты в рубли
  3. Посчитать каккое количество кредитов у каждого заемщика по информации от бюро

Начнем с очистки таблицы от ненужных столбцов:

PreFinalDS = PreFinalDS.drop(['bki_request_date',
                              'inf_confirm_date',
                              'pmt_string_start',
                              'interest_rate',
                              'open_date',
                              'final_pmt_date',
                              'inf_confirm_date_max'], axis=1)

Далее переведем все кредитные лимиты к рублям. Для простоты я взял курсы валют на текущий момент. Хотя правильнее наверное было бы брать курс на момент открытия счета. Еще один ньюанс, в том, что для анализа нам надо убрать текстовое поле ‘сurrency’, поэтому после перевода валют в рубли мы проведем с этим полем манипуляцию, которые мы провели с полями выше:

curs = DataFrame([33.13,44.99,36.49,1], index=['USD','EUR','GHF','RUB'], columns=['crs'])
PreFinalDS = PreFinalDS.merge(curs, 'left', left_on='currency', right_index=True)
PreFinalDS.credit_limit = PreFinalDS.credit_limit * PreFinalDS.crs

#выделяем значения в отдельные столбцы
vals = ['RUB','USD','EUR','CHF']
PMTstr = DataFrame([{'currency_%s' % (str(j)): str(i).count(str(j)) for j in vals} for i in PreFinalDS.currency])
PreFinalDS = PreFinalDS.join(PMTstr).drop(['currency','crs'], axis=1)

Итак перед заключительной группировкой добавим к нашему набору поле заполненное единицами. Т.е. когда мы выполним последнюю групировку, сумма по нему даст количество кредитов у заемщика:

PreFinalDS['count_credit'] = 1

Теперь, когда у нас в наборе данных все данные количественные, можно заполнить пробелы в данных 0 и выполнить заключительную группировку по клиенту:

PreFinalDS.fillna(0, inplace=True)
FinalDF = PreFinalDS.groupby('tcs_customer_id').sum()
FinalDF
<class 'pandas.core.frame.DataFrame'>
Int64Index: 50000 entries, 1 to 50000
Data columns (total 66 columns):
credit_limit        50000  non-null values
fact_close_date     50000  non-null values
outstanding         50000  non-null values
next_pmt            50000  non-null values
curr_balance_amt    50000  non-null values
current_delq        50000  non-null values
ttl_delq_5          50000  non-null values
ttl_delq_5_29       50000  non-null values
ttl_delq_30         50000  non-null values
ttl_delq_30_59      50000  non-null values
ttl_delq_60_89      50000  non-null values
ttl_delq_90_plus    50000  non-null values
delq_balance        50000  non-null values
max_delq_balance    50000  non-null values
pmt_string_84m_0    50000  non-null values
pmt_string_84m_1    50000  non-null values
pmt_string_84m_2    50000  non-null values
pmt_string_84m_3    50000  non-null values
pmt_string_84m_4    50000  non-null values
pmt_string_84m_5    50000  non-null values
pmt_string_84m_6    50000  non-null values
pmt_string_84m_7    50000  non-null values
pmt_string_84m_8    50000  non-null values
pmt_string_84m_9    50000  non-null values
pmt_string_84m_A    50000  non-null values
pmt_string_84m_X    50000  non-null values
pmt_freq_1          50000  non-null values
pmt_freq_2          50000  non-null values
pmt_freq_3          50000  non-null values
pmt_freq_4          50000  non-null values
pmt_freq_5          50000  non-null values
pmt_freq_6          50000  non-null values
pmt_freq_7          50000  non-null values
pmt_freq_A          50000  non-null values
pmt_freq_B          50000  non-null values
type_1              50000  non-null values
type_10             50000  non-null values
type_11             50000  non-null values
type_12             50000  non-null values
type_13             50000  non-null values
type_14             50000  non-null values
type_4              50000  non-null values
type_6              50000  non-null values
type_7              50000  non-null values
type_9              50000  non-null values
type_99             50000  non-null values
status_0            50000  non-null values
status_12           50000  non-null values
status_13           50000  non-null values
status_14           50000  non-null values
status_21           50000  non-null values
status_52           50000  non-null values
status_61           50000  non-null values
relationship_1      50000  non-null values
relationship_2      50000  non-null values
relationship_4      50000  non-null values
relationship_5      50000  non-null values
relationship_9      50000  non-null values
bureau_cd_1         50000  non-null values
bureau_cd_2         50000  non-null values
bureau_cd_3         50000  non-null values
currency_CHF        50000  non-null values
currency_EUR        50000  non-null values
currency_RUB        50000  non-null values
currency_USD        50000  non-null values
count_credit        50000  non-null values
dtypes: float64(7), int64(59)

Предварительный анализ

Ну что же первичная обработка данных завершена и можно приступить к их анализу. Для начала разделим наши данные на обучающую и тестовую выборки. В этом нам поможет столбец “sample_type” из SampleCustomers, по нему как раз сделано такое разделение.

Для того чтобы разбить наш обработанный DataFrame, достаточно объеденить его с SampleCustomers поиграться фильтрами:

SampleCustomers.set_index('tcs_customer_id', inplace=True)
UnionDF = FinalDF.join(SampleCustomers)
trainDF = UnionDF[UnionDF.sample_type == 'train'].drop(['sample_type'], axis=1)
testDF = UnionDF[UnionDF.sample_type == 'test'].drop(['sample_type'], axis=1)

Далее давайте посмотрим, как признаки коррелирубт между собой, для этого построим матрицу с коэффициентами корреляции признаков. С помощью pandas это можно сделать одной командой:

CorrKoef = trainDF.corr()
CorrKoef
<class 'pandas.core.frame.DataFrame'>
Index: 67 entries, credit_limit to bad
Data columns (total 67 columns):
credit_limit        61  non-null values
fact_close_date     61  non-null values
outstanding         61  non-null values
next_pmt            61  non-null values
curr_balance_amt    61  non-null values
current_delq        61  non-null values
ttl_delq_5          61  non-null values
ttl_delq_5_29       61  non-null values
ttl_delq_30         61  non-null values
ttl_delq_30_59      61  non-null values
ttl_delq_60_89      61  non-null values
ttl_delq_90_plus    61  non-null values
delq_balance        61  non-null values
max_delq_balance    61  non-null values
pmt_string_84m_0    61  non-null values
pmt_string_84m_1    61  non-null values
pmt_string_84m_2    61  non-null values
pmt_string_84m_3    61  non-null values
pmt_string_84m_4    61  non-null values
pmt_string_84m_5    61  non-null values
pmt_string_84m_6    0  non-null values
pmt_string_84m_7    61  non-null values
pmt_string_84m_8    0  non-null values
pmt_string_84m_9    61  non-null values
pmt_string_84m_A    61  non-null values
pmt_string_84m_X    61  non-null values
pmt_freq_1          61  non-null values
pmt_freq_2          61  non-null values
pmt_freq_3          61  non-null values
pmt_freq_4          61  non-null values
pmt_freq_5          0  non-null values
pmt_freq_6          61  non-null values
pmt_freq_7          61  non-null values
pmt_freq_A          0  non-null values
pmt_freq_B          0  non-null values
type_1              61  non-null values
type_10             61  non-null values
type_11             61  non-null values
type_12             61  non-null values
type_13             61  non-null values
type_14             61  non-null values
type_4              61  non-null values
type_6              61  non-null values
type_7              61  non-null values
type_9              61  non-null values
type_99             61  non-null values
status_0            61  non-null values
status_12           0  non-null values
status_13           61  non-null values
status_14           61  non-null values
status_21           61  non-null values
status_52           61  non-null values
status_61           61  non-null values
relationship_1      61  non-null values
relationship_2      61  non-null values
relationship_4      61  non-null values
relationship_5      61  non-null values
relationship_9      61  non-null values
bureau_cd_1         61  non-null values
bureau_cd_2         61  non-null values
bureau_cd_3         61  non-null values
currency_CHF        61  non-null values
currency_EUR        61  non-null values
currency_RUB        61  non-null values
currency_USD        61  non-null values
count_credit        61  non-null values
bad                 61  non-null values
dtypes: float64(67)

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

FieldDrop = [i for i in CorrKoef if CorrKoef[i].isnull().drop_duplicates().values[0]]
FieldDrop
['pmt_string_84m_6',
 'pmt_string_84m_8',
 'pmt_freq_5',
 'pmt_freq_A',
 'pmt_freq_B',
 'status_12']

Следующим шагом мы найдем поля которые коррелируют между собой, используя нашу матрицу:

CorField = []
for i in CorrKoef:
    for j in CorrKoef.index[CorrKoef[i] > 0.9]:
        if i <> j and j not in CorField and i not in CorField:
            CorField.append(j)
            print "%s-->%s: r^2=%f" % (i,j, CorrKoef[i][CorrKoef.index==j].values[0])
fact_close_date-->status_13: r^2=0.997362
ttl_delq_5_29-->ttl_delq_30: r^2=0.954740
ttl_delq_5_29-->pmt_string_84m_A: r^2=0.925870
ttl_delq_30_59-->pmt_string_84m_2: r^2=0.903337
ttl_delq_90_plus-->pmt_string_84m_5: r^2=0.978239
delq_balance-->max_delq_balance: r^2=0.986967
pmt_freq_3-->relationship_1: r^2=0.909820
pmt_freq_3-->currency_RUB: r^2=0.910620
pmt_freq_3-->count_credit: r^2=0.911109

Итак, исходя из связей которые мы получили на предыдущем шаге, мы можем добавить в список удаления следующие поля:

FieldDrop =FieldDrop + ['fact_close_date','ttl_delq_30',
                        'pmt_string_84m_5',
                        'pmt_string_84m_A',
                        'pmt_string_84m_A',
                        'max_delq_balance',
                        'relationship_1',
                        'currency_RUB',
                        'count_credit']
newtr = trainDF.drop(FieldDrop, axis=1)

Построение и выбор модели

Ну что же первичные данные обработаны и тепере можно перейти к построению модели.

Отделим признак класса от обучающей выборки:

target = newtr.bad.values
train = newtr.drop('bad', axis=1).values

Теперь давайте уменьшим размерность нашей выборки, дабы взять только значемые параметры. Для этого воспользуемся методом главных компонент и его реализацией PCA() в модуле sklearn. В параметре мы передаем количество конпонент, которые мы хотим сохранить(я выбрал 20, т.к. при них результаты моделей практически не отличались от результатов по исходным данным)

coder = PCA(n_components=20)
train = coder.fit_transform(train)

Пришло время для определения моделей классификации. Возьмем несколько различных алгоритмов и сравним результаты их работы при попмощи характеристики Area Under ROC Curve (auc). Длдя моделирования будут рассмотрены следующие алгоритмы:

models = []
models.append(RandomForestClassifier(n_estimators=165, max_depth=4, criterion='entropy'))
models.append(GradientBoostingClassifier(max_depth =4))
models.append(KNeighborsClassifier(n_neighbors=20))
models.append(GaussianNB())

Итак модели выбраны. Давайте сейчас разобъем нашу обучающую выборку на 2 подвыборки: тестовую и обучающую. Данное действие нужно чтобы мы могли почситать характеристику auc для наших моделей. Разбиение можно провести функцией train_test_split() из модуля sklearn:

TRNtrain, TRNtest, TARtrain, TARtest = train_test_split(train, target, test_size=0.3, random_state=0)

Осталось осталось обучить наши модели и оценить результат.

Для расчета характеристики auc есть 2 пути:

  1. Стандартными средствами модуля sklearn при помощи функции roc_auc_score или auc
  2. С помощью стороннего пакета ml_metrics и функции auc()

Я воспользуюсь вторым способом, т.к. первый был показан в предыдущей статье. Пакет ml_metrics является очень полезным дополнением к sklearn, т.к. в нем присутствуют некоторые метрики, которых нет в sklearn.

Итак, построим ROC кривые и посчитаем их площади:

plt.figure(figsize=(10, 10)) 
for model in models:
    model.fit(TRNtrain, TARtrain)
    pred_scr = model.predict_proba(TRNtest)[:, 1]
    fpr, tpr, thresholds = roc_curve(TARtest, pred_scr)
    roc_auc = ml_metrics.auc(TARtest, pred_scr)
    md = str(model)
    md = md[:md.find('(')]
    pl.plot(fpr, tpr, label='ROC fold %s (auc = %0.2f)' % (md, roc_auc))

pl.plot([0, 1], [0, 1], '--', color=(0.6, 0.6, 0.6))
pl.xlim([0, 1])
pl.ylim([0, 1])
pl.xlabel('False Positive Rate')
pl.ylabel('True Positive Rate')
pl.title('Receiver operating characteristic example')
pl.legend(loc="lower right")
pl.show()

png

Итак, по результатам анализа наших моделей можно сказать, что лучше всего себя показал градиентный бустинг, его точность порядка 69%. Соответственно для обучения тестовой выборки мы выберем его. Давайте заполним информацию в тестовой выборке, предварительно обработав ее до нужного формата:

#приводим тестовую выборку к нужному формату
FieldDrop.append('bad')
test = testDF.drop(FieldDrop, axis=1).values
test = coder.fit_transform(test)

#обучаем модель
model = models[1]
model.fit(train, target)

#записываем результат
testDF.bad = model.predict(test)

Заключение

В качестве заключения хотелось бы отметить, что полученная точность модели в 69%, является не достаточно хорошей, но большей точности я добиться не смог. Хотелось бы отметить, тот факт, что при построении модели по полной размерности, т.е. без учета коррелируемых столбцов и сокращения размерности, она дала так же 69% точности (это можно легко проверить используя набор trainDF для обучения модели)

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

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