Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Пример решения задачи кредитного скоринга c поддержкой связки python pandas scikit-learn

Anna | 16.06.2014 | нет комментариев
Добродушный день, уважаемые читатели.
Незадолго, бродя по просторам всеобщей паутины, я наткнулся на турнир, тот, что проводился банком ТКС в начале этого года. Ознакомившись с заданиями, я решил проверить свои навыки в обзоре данных на них.
Начать проверку я решил с задачи о скоринге (Задание №3). Для ее решения я, как неизменно, применял Python с аналитическими модулями pandas и scikit-learn.

Изложение данных и постановка задачи

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

Изложение комплекта

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

Задача состоит в том, Дабы на выборке «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 =',')

print SampleAccounts

image

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

Из условий задачи дозволено предположить, что комплект SampleAccounts содержит несколько записей по одному заемщику давайте проверим это:

SampleAccounts.tcs_customer_id.drop_duplicates().count(), SampleAccounts.tcs_customer_id.count()

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

SampleAccounts[['tcs_customer_id','open_date','final_pmt_date','credit_limit','currency']].drop_duplicates()

Следственно, когда мы получили список кредитов, мы сумеем вывести какую-либо всеобщую информацию по всякому элементу списка. Т.е. дозволено было бы взять связку из перечисленных выше полей и сделать ее индексом, касательно которого мы бы производили последующие манипуляции, но, к сожалению, здесь нас подстерегает один малоприятный момент. Он заключается в том, что поле ‘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
  • pmt_freq
  • type
  • status
  • relationship
  • bureau_cd

Код для их реформирования приведен ниже:

# преобразуем 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 значения:

  • 0 — не было последнего платежа
  • 1 — конечный платеж был

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

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)

Выходит пвыборки, чтобы взять только важные параметры. Для этого воспользуемся способом основных компонент и его реализацией 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()

Я воспользуюсь вторым методом, т.к. 1-й был показан в предыдущей статье. Пакет 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()

image
Выходит, по итогам обзора наших моделей дозволено сказать, что отменнее каждого себя показал градиентный бустинг, его точность порядка 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%, при существенных затратах по времени.
Также хотелось бы подметить, что, к сожалению на тестовой выборке итог проверить не удалось, т.к. не уложился в сроки проведения турнира.

 

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB