Решение задачи кредитного скоринга методом логистической регрессии

МЕНЮ


Новости ИИ
Поиск

ТЕМЫ


Внедрение ИИНовости ИИРобототехника, БПЛАТрансгуманизмЛингвистика, рбработка текстаБиология, теория эволюцииВиртулаьная и дополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информации

АРХИВ


Июнь 2017
Май 2017
Апрель 2017
Март 2017
Февраль 2017
Январь 2017
Декабрь 2016
Ноябрь 2016
Октябрь 2016
Сентябрь 2016
Август 2016
Июль 2016
Июнь 2016
Май 2016
Апрель 2016
Март 2016
Февраль 2016
Январь 2016
0000

RSS


RSS новости
птичий грипп

Новостная лента форума ailab.ru

Отучившись на нескольких онлайн-курсах, попробовал занять позицию, связанную с Machine Learning — на входе получил тестовое задание о кредитном скоринге. Свое решение которой здесь и привожу:

Задание

Данные содержат информацию о выданных кредитах, требуется предсказать вероятность успешного возврата кредита.

Тренировочная выборка содержится в файле train.csv, тестовая — test.csv.

Информация о значениях признаков содержится в файле feature_descr.xlsx.

Целевой признак — loan_status (бинарный). 1 означает что кредит успешно вернули.

В рамках тестового задания вам предлагается:

  • Обучить модель на предоставленных данных, найти качество полученной модели.
  • Записать предсказания (вероятности) для тестового набора в файл results.csv
  • Продемонстрировать результаты анализа в графическом виде (ROC-curve)

Тщательный выбор фич и подбор гиперпараметров можно не проводить.

Решение

import pandas as pd

import sys print "Python version: {}".format(sys.version) print "Pandas version: {}".format(pd.__version__)

Python version: 2.7.13 |Anaconda 4.3.1 (64-bit)| (default, Dec 19 2016, 13:29:36) [MSC v.1500 64 bit (AMD64)] Pandas version: 0.19.2

Тут стоит заметить что использовался Python 2.7

pd.set_option('display.max_columns', 500) pd.set_option('display.width', 1000)

Читаем данные из train.csv, используя record_id как индекс:

train = pd.read_csv('train.csv', index_col='record_id') train.head()

train.shape

(200189, 35)

Смотрим, какие типы данных в столбцах:

train.dtypes

loan_amnt                     float64 term                           object int_rate                      float64 installment                   float64 grade                          object sub_grade                      object emp_title                      object emp_length                     object home_ownership                 object annual_inc                    float64 verification_status            object issue_d                        object loan_status                     int64 pymnt_plan                     object purpose                        object zip_code                       object addr_state                     object dti                           float64 delinq_2yrs                   float64 earliest_cr_line               object inq_last_6mths                float64 mths_since_last_delinq        float64 open_acc                      float64 pub_rec                       float64 revol_bal                     float64 revol_util                    float64 total_acc                     float64 initial_list_status            object collections_12_mths_ex_med    float64 policy_code                   float64 application_type               object acc_now_delinq                float64 tot_coll_amt                  float64 tot_cur_bal                   float64 total_rev_hi_lim              float64 dtype: object

Исследуем поля на наличие пропусков:

train.isnull().sum()

loan_amnt                          0 term                               0 int_rate                           0 installment                        0 grade                              0 sub_grade                          0 emp_title                      11124 emp_length                         0 home_ownership                     0 annual_inc                         0 verification_status                0 issue_d                            0 loan_status                        0 pymnt_plan                         0 purpose                            0 zip_code                           0 addr_state                         0 dti                                0 delinq_2yrs                        0 earliest_cr_line                   0 inq_last_6mths                     0 mths_since_last_delinq        110568 open_acc                           0 pub_rec                            0 revol_bal                          0 revol_util                       154 total_acc                          0 initial_list_status                0 collections_12_mths_ex_med        44 policy_code                        0 application_type                   0 acc_now_delinq                     0 tot_coll_amt                   47957 tot_cur_bal                    47957 total_rev_hi_lim               47957 dtype: int64

Следующие столбцы имеют пропуски:

emp_title — The job title supplied by the Borrower when applying for the loan

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

train['is_title_known'] = train['emp_title'].map(lambda x: 0 if x == 'n/a' else 1) train.drop('emp_title', axis=1, inplace=True)

mths_since_last_delinq — The number of months since the borrower's last delinquency

— кол-во месяцев с момента последнего неосуществления платежа в установленный срок. Заменить пропуски на 0 будет неправильно, заменим их на max в этом столбце и добавим столбец is_delinq_occurs с флагом, был ли факт неплатежа ранее

import numpy as np import math train['is_delinq_occurs'] = train['mths_since_last_delinq'].map(lambda x: 0 if math.isnan(x) else 1)  max_mths_since_last_delinq = np.nanmax(train.mths_since_last_delinq.values) train['mths_since_last_delinq'].fillna(max_mths_since_last_delinq, inplace=True)

revol_util — Revolving line utilization rate, or the amount of credit the borrower is using relative to all available revolving credit

collections_12_mths_ex_med — Number of collections in 12 months excluding medical collections

tot_coll_amt — Total collection amounts ever owed

tot_cur_bal — Total current balance of all accounts

total_rev_hi_lim — Total revolving high credit/credit limit

Мне неизвестны тонкости этого бизнес-домена, поэтому заменяем пропуски на нули с помощью функции fillna():

train.fillna(0, inplace=True) train.isnull().sum()

loan_amnt                     0 term                          0 int_rate                      0 installment                   0 grade                         0 sub_grade                     0 emp_length                    0 home_ownership                0 annual_inc                    0 verification_status           0 issue_d                       0 loan_status                   0 pymnt_plan                    0 purpose                       0 zip_code                      0 addr_state                    0 dti                           0 delinq_2yrs                   0 earliest_cr_line              0 inq_last_6mths                0 mths_since_last_delinq        0 open_acc                      0 pub_rec                       0 revol_bal                     0 revol_util                    0 total_acc                     0 initial_list_status           0 collections_12_mths_ex_med    0 policy_code                   0 application_type              0 acc_now_delinq                0 tot_coll_amt                  0 tot_cur_bal                   0 total_rev_hi_lim              0 is_title_known                0 is_delinq_occurs              0 dtype: int64

— Пропусков теперь нет

Разбиваем данные на X и Y:

def extract_XY(data):     X = data.drop(['loan_status'], axis=1)     Y = data['loan_status']     return X, Y  X, Y = extract_XY(train)  print X.shape, Y.shape

(200189, 35) (200189L,)

Предварительно надо разобраться с нечисловыми столбцами

Добавим методы для LabelEncoder и OneHotEncoder:

from sklearn.preprocessing import LabelEncoder from sklearn.preprocessing import OneHotEncoder  # Добавляет в DataFrame df новый столбец с именем column_name+'_le', содержащий номера категорий,  # соответствующие столбцу column_name. Исходный столбец column_name удаляется # def encode_with_LabelEncoder(df, column_name):     label_encoder = LabelEncoder()     label_encoder.fit(df[column_name])     df[column_name+'_le'] = label_encoder.transform(df[column_name])     df.drop([column_name], axis=1, inplace=True)     return label_encoder  # Кодирование с использованием ранее созданного LabelEncoder # def encode_with_existing_LabelEncoder(df, column_name, label_encoder):     df[column_name+'_le'] = label_encoder.transform(df[column_name])     df.drop([column_name], axis=1, inplace=True)  # Вначале кодирует столбец column_name при помощи LabelEncoder, потом добавляет в DataFrame df новые столбцы  # с именами column_name=<категория_i>. Столбцы column_name и column_name+'_le' удаляются # Usage: df, label_encoder = encode_with_OneHotEncoder_and_delete_column(df, column_name) # def encode_with_OneHotEncoder_and_delete_column(df, column_name):     le_encoder = encode_with_LabelEncoder(df, column_name)     return perform_dummy_coding_and_delete_column(df, column_name, le_encoder), le_encoder  # То же, что предыдущий метод, но при помощи уже существующего LabelEncoder # def encode_with_OneHotEncoder_using_existing_LabelEncoder_and_delete_column(df, column_name, le_encoder):     encode_with_existing_LabelEncoder(df, column_name, le_encoder)     return perform_dummy_coding_and_delete_column(df, column_name, le_encoder)  # Реализует Dummy-кодирование # def perform_dummy_coding_and_delete_column(df, column_name, le_encoder):     oh_encoder = OneHotEncoder(sparse=False)     oh_features = oh_encoder.fit_transform(df[column_name+'_le'].values.reshape(-1,1))     ohe_columns=[column_name + '=' + le_encoder.classes_[i] for i in range(oh_features.shape[1])]      df.drop([column_name+'_le'], axis=1, inplace=True)      df_with_features = pd.DataFrame(oh_features, columns=ohe_columns)     df_with_features.index = df.index     return pd.concat([df, df_with_features], axis=1)

term — The number of payments on the loan. Values are in months and can be either 36 or 60.

— Кол-во платежей, стоит оставить

import numpy as np print np.unique(X['term'])

[' 36 months' ' 60 months']

term_le_encoder = encode_with_LabelEncoder(X,'term')

grade, sub_grade — loan grade & subgrade

— Некие "класс и подкласс заема", grade соответствует риску займа (с нарастанием риска от A к G), sub_grade — то же, только c больше детализацией, — стоит оставить

print np.unique(X['grade']) print np.unique(X['sub_grade'])

['A' 'B' 'C' 'D' 'E' 'F' 'G'] ['A1' 'A2' 'A3' 'A4' 'A5' 'B1' 'B2' 'B3' 'B4' 'B5' 'C1' 'C2' 'C3' 'C4' 'C5'  'D1' 'D2' 'D3' 'D4' 'D5' 'E1' 'E2' 'E3' 'E4' 'E5' 'F1' 'F2' 'F3' 'F4' 'F5'  'G1' 'G2' 'G3' 'G4' 'G5']

grade_le_encoder = encode_with_LabelEncoder(X,'grade') sub_grade_le_encoder = encode_with_LabelEncoder(X,'sub_grade')

emp_length — Employment length in years. Possible values are between 0 and 10 where 0 means less than one year and 10 means ten or more years

— Срок занятости — стоит оставить

print np.unique(X['emp_length'])

['1 year' '10+ years' '2 years' '3 years' '4 years' '5 years' '6 years'  '7 years' '8 years' '9 years' '< 1 year' 'n/a']

X, emp_length_le_encoder = encode_with_OneHotEncoder_and_delete_column(X,'emp_length')

home_ownership — The home ownership status provided by the borrower during registration. Our values are: RENT, OWN, MORTGAGE, OTHER

— Флажок, принадлежит ли владельцу его текущий дом — стоит оставить

print np.unique(X['home_ownership'])

['ANY' 'MORTGAGE' 'NONE' 'OTHER' 'OWN' 'RENT']

X, home_ownership_le_encoder = encode_with_OneHotEncoder_and_delete_column(X,'home_ownership')

verification_status

— В описании столбцов данного столбца нету, но судя из названия — это информация о том, являются ли данные о заемщике проверенными. Стоит оставить

print np.unique(X['verification_status'])

['Not Verified' 'Source Verified' 'Verified']

X, verification_status_le_encoder = encode_with_OneHotEncoder_and_delete_column(X,'verification_status')

issue_d — The month which the loan was funded

— Месяц (и год), в который предоставлялся заем (вроде как). В период спада экономики возвратность займов может падать — стоит оставить

print np.unique(X['issue_d'])

['Apr-2008' 'Apr-2009' 'Apr-2010' 'Apr-2011' 'Apr-2012' 'Apr-2013'  'Apr-2014' 'Apr-2015' 'Aug-2007' 'Aug-2008' 'Aug-2009' 'Aug-2010'  'Aug-2011' 'Aug-2012' 'Aug-2013' 'Aug-2014' 'Aug-2015' 'Dec-2007'  'Dec-2008' 'Dec-2009' 'Dec-2010' 'Dec-2011' 'Dec-2012' 'Dec-2013'  'Dec-2014' 'Dec-2015' 'Feb-2008' 'Feb-2009' 'Feb-2010' 'Feb-2011'  'Feb-2012' 'Feb-2013' 'Feb-2014' 'Feb-2015' 'Jan-2008' 'Jan-2009'  'Jan-2010' 'Jan-2011' 'Jan-2012' 'Jan-2013' 'Jan-2014' 'Jan-2015'  'Jul-2007' 'Jul-2008' 'Jul-2009' 'Jul-2010' 'Jul-2011' 'Jul-2012'  'Jul-2013' 'Jul-2014' 'Jul-2015' 'Jun-2007' 'Jun-2008' 'Jun-2009'  'Jun-2010' 'Jun-2011' 'Jun-2012' 'Jun-2013' 'Jun-2014' 'Jun-2015'  'Mar-2008' 'Mar-2009' 'Mar-2010' 'Mar-2011' 'Mar-2012' 'Mar-2013'  'Mar-2014' 'Mar-2015' 'May-2008' 'May-2009' 'May-2010' 'May-2011'  'May-2012' 'May-2013' 'May-2014' 'May-2015' 'Nov-2007' 'Nov-2008'  'Nov-2009' 'Nov-2010' 'Nov-2011' 'Nov-2012' 'Nov-2013' 'Nov-2014'  'Nov-2015' 'Oct-2007' 'Oct-2008' 'Oct-2009' 'Oct-2010' 'Oct-2011'  'Oct-2012' 'Oct-2013' 'Oct-2014' 'Oct-2015' 'Sep-2007' 'Sep-2008'  'Sep-2009' 'Sep-2010' 'Sep-2011' 'Sep-2012' 'Sep-2013' 'Sep-2014'  'Sep-2015']

Датам можно поставить в соответствие числа так, что более поздней дате соответствует большее число:

def month_to_decimal(month):     month_dict = {'Jan':0, 'Feb':1/12., 'Mar':2/12., 'Apr':3/12., 'May':4/12., 'Jun':5/12.,       'Jul':6/12., 'Aug':7/12., 'Sep':8/12., 'Oct':9/12., 'Nov':10/12., 'Dec':11/12.}     return month_dict[month]  def convert_date(month_year):     month_and_year = month_year.split('-')     return float(month_and_year[1]) + month_to_decimal(month_and_year[0])  # Some check convert_date('Mar-2011')

2011.1666666666667

def encode_with_func(df, column_name, func_name):     df[column_name+'_le'] = df[column_name].map(func_name)     df.drop(column_name, axis=1, inplace=True)  encode_with_func(X, 'issue_d', convert_date)

pymnt_plan — Indicates if a payment plan has been put in place for the loan

— Флажок был ли план платежей, стоит оставить

print np.unique(X['pymnt_plan'])

['n' 'y']

pymnt_plan_le_encoder = encode_with_LabelEncoder(X,'pymnt_plan')

purpose — A category provided by the borrower for the loan request

— Категория, для чего брался заем, стоит оставить

print np.unique(X['purpose'])

['car' 'credit_card' 'debt_consolidation' 'educational' 'home_improvement'  'house' 'major_purchase' 'medical' 'moving' 'other' 'renewable_energy'  'small_business' 'vacation' 'wedding']

X,purpose_le_encoder = encode_with_OneHotEncoder_and_delete_column(X,'purpose')

zip_code — The first 3 numbers of the zip code provided by the borrower in the loan application

addr_state — The state provided by the borrower in the loan application

— Почтовый индекс и адрес

print len(np.unique(X['zip_code'])) print len(np.unique(X['addr_state']))

877 51

X.drop(['zip_code'], axis=1, inplace=True) addr_state_le_encoder = encode_with_LabelEncoder(X,'addr_state')

earliest_cr_line — The month the borrower's earliest reported credit line was opened

print np.unique(X['earliest_cr_line'])

['Apr-1964' 'Apr-1965' 'Apr-1966' 'Apr-1967' 'Apr-1968' 'Apr-1969'  'Apr-1970' 'Apr-1971' 'Apr-1972' 'Apr-1973' 'Apr-1974' 'Apr-1975'  'Apr-1976' 'Apr-1977' 'Apr-1978' 'Apr-1979' 'Apr-1980' 'Apr-1981'  'Apr-1982' 'Apr-1983' 'Apr-1984' 'Apr-1985' 'Apr-1986' 'Apr-1987'                                         ...     'Sep-1989' 'Sep-1990' 'Sep-1991' 'Sep-1992' 'Sep-1993' 'Sep-1994'  'Sep-1995' 'Sep-1996' 'Sep-1997' 'Sep-1998' 'Sep-1999' 'Sep-2000'  'Sep-2001' 'Sep-2002' 'Sep-2003' 'Sep-2004' 'Sep-2005' 'Sep-2006'  'Sep-2007' 'Sep-2008' 'Sep-2009' 'Sep-2010' 'Sep-2011']

Формат аналогичен столбцу issue_d, кодируем той же функцией convert_date:

encode_with_func(X, 'earliest_cr_line', convert_date)

initial_list_status — The initial listing status of the loan. Possible values are – W, F

— Некий параметр заема, оставляем

print np.unique(X['initial_list_status'])

['f' 'w']

initial_list_status_le_encoder = encode_with_LabelEncoder(X,'initial_list_status')

application_type — Indicates whether the loan is an individual application or a joint application with two co-borrowers

— Индикатор, заем брался одним человеком или в группе с кем-то. Стоит оставить

print np.unique(X['application_type'])

['INDIVIDUAL' 'JOINT']

application_type_le_encoder = encode_with_LabelEncoder(X,'application_type')

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

X.dtypes

loan_amnt                              float64 int_rate                               float64 installment                            float64 annual_inc                             float64 dti                                    float64 delinq_2yrs                            float64 inq_last_6mths                         float64 mths_since_last_delinq                 float64 open_acc                               float64 pub_rec                                float64 revol_bal                              float64 revol_util                             float64 total_acc                              float64 collections_12_mths_ex_med             float64 policy_code                            float64 acc_now_delinq                         float64 tot_coll_amt                           float64 tot_cur_bal                            float64 total_rev_hi_lim                       float64 is_title_known                           int64 is_delinq_occurs                         int64 term_le                                  int64 grade_le                                 int64 sub_grade_le                             int64 emp_length=1 year                      float64 emp_length=10+ years                   float64 emp_length=2 years                     float64 emp_length=3 years                     float64 emp_length=4 years                     float64 emp_length=5 years                     float64                                         ...    emp_length=n/a                         float64 home_ownership=ANY                     float64 home_ownership=MORTGAGE                float64 home_ownership=NONE                    float64 home_ownership=OTHER                   float64 home_ownership=OWN                     float64 home_ownership=RENT                    float64 verification_status=Not Verified       float64 verification_status=Source Verified    float64 verification_status=Verified           float64 issue_d_le                             float64 pymnt_plan_le                            int64 purpose=car                            float64 purpose=credit_card                    float64 purpose=debt_consolidation             float64 purpose=educational                    float64 purpose=home_improvement               float64 purpose=house                          float64 purpose=major_purchase                 float64 purpose=medical                        float64 purpose=moving                         float64 purpose=other                          float64 purpose=renewable_energy               float64 purpose=small_business                 float64 purpose=vacation                       float64 purpose=wedding                        float64 addr_state_le                            int64 earliest_cr_line_le                    float64 initial_list_status_le                   int64 application_type_le                      int64 dtype: object

X.shape

(200189, 65)

Признаки подготовлены

Для решения выбрал логистическую регрессию — она дает ответ в виде набора вероятностей и работает достаточно быстро

При решении применяем кросс-валидацию по 5 блокам с перемешиванием

Качеством считаем площадь под ROC-кривой — AUC-ROC величину

from sklearn.cross_validation import KFold from sklearn.metrics import roc_auc_score from sklearn.cross_validation import cross_val_score from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression  records_count = Y.count() kf = KFold(n=records_count, n_folds=5, shuffle=True)  def my_scorer(estimator, testX, testY):     predicted_testY = estimator.predict_proba(testX)[:, 1]     return roc_auc_score(testY, predicted_testY)  scaler = StandardScaler() scaledX = scaler.fit_transform(X)  def LogR_teach(C_value):     clf = LogisticRegression(penalty='l2', C=C_value)     return cross_val_score(clf, scaledX, Y, cv=kf, scoring=my_scorer).mean()  def check_quality_for_different_C():     for power in range(-4, 2):         C = math.pow(10, power)         quality = LogR_teach(C)         print 'C=', C, ', quality=', quality

check_quality_for_different_C()

C= 0.0001 , quality= 0.707991113828 C= 0.001 , quality= 0.709654648448 C= 0.01 , quality= 0.709779198127 C= 0.1 , quality= 0.709776206257 C= 1.0 , quality= 0.709775629602 C= 10.0 , quality= 0.709775731556

Лучшее качество 0.71 достигается при С=0.1

from sklearn.model_selection import train_test_split  X_train, X_test, y_train, y_test = train_test_split(scaledX, Y, test_size=.2, random_state=0) clf = LogisticRegression(penalty='l2', C=0.1) clf.fit(X_train, y_train) y_score = clf.predict_proba(X_test)[:, 1]

Чертим ROC-кривую:

import matplotlib.pyplot as plt from sklearn.metrics import roc_curve  plt.figure() line_width = 2 fpr, tpr, thresholds = roc_curve(y_test, y_score) plt.plot(fpr, tpr, color='darkorange', lw=line_width, label='LogRegression, C=0.1') plt.plot([0, 1], [0, 1], color='navy', lw=line_width, linestyle='--') plt.xlim([0.0, 1.0]) plt.ylim([0.0, 1.0]) plt.xlabel('False Positive Rate') plt.ylabel('True Positive Rate') plt.title('Receiver operating characteristic') plt.legend(loc="lower right") plt.show()

image

Вычислим предсказания для тестового набора

Загружаем данные:

test = pd.read_csv('test.csv', index_col='record_id')

Делаем действия аналогичные проделанным с train набором:

test['is_title_known'] = test['emp_title'].map(lambda x: 0 if x == 'n/a' else 1) test.drop('emp_title', axis=1, inplace=True)  test['is_delinq_occurs'] = test['mths_since_last_delinq'].map(lambda x: 0 if math.isnan(x) else 1) max_mths_since_last_delinq = np.nanmax(test.mths_since_last_delinq.values) test['mths_since_last_delinq'].fillna(max_mths_since_last_delinq, inplace=True)  test.fillna(0, inplace=True) test.isnull().sum()

loan_amnt                     0 term                          0 int_rate                      0 installment                   0 grade                         0 sub_grade                     0 emp_length                    0 home_ownership                0 annual_inc                    0 verification_status           0 issue_d                       0 loan_status                   0 pymnt_plan                    0 purpose                       0 zip_code                      0 addr_state                    0 dti                           0 delinq_2yrs                   0 earliest_cr_line              0 inq_last_6mths                0 mths_since_last_delinq        0 open_acc                      0 pub_rec                       0 revol_bal                     0 revol_util                    0 total_acc                     0 initial_list_status           0 collections_12_mths_ex_med    0 policy_code                   0 application_type              0 acc_now_delinq                0 tot_coll_amt                  0 tot_cur_bal                   0 total_rev_hi_lim              0 is_title_known                0 is_delinq_occurs              0 dtype: int64

— Пропусков нету, как и ожидаем

print test.shape

(66730, 36)

Подготовим нечисловые столбцы:

Используем энкодеры созданные ранее, при обработке train набора

encode_with_existing_LabelEncoder(test, 'term', term_le_encoder) encode_with_existing_LabelEncoder(test, 'grade', grade_le_encoder) encode_with_existing_LabelEncoder(test, 'sub_grade', sub_grade_le_encoder)  test = encode_with_OneHotEncoder_using_existing_LabelEncoder_and_delete_column(test, 'emp_length', emp_length_le_encoder) test = encode_with_OneHotEncoder_using_existing_LabelEncoder_and_delete_column(test, 'home_ownership', home_ownership_le_encoder) test = encode_with_OneHotEncoder_using_existing_LabelEncoder_and_delete_column(test, 'verification_status', verification_status_le_encoder)  encode_with_func(test, 'issue_d', convert_date) encode_with_existing_LabelEncoder(test, 'pymnt_plan', pymnt_plan_le_encoder)  test = encode_with_OneHotEncoder_using_existing_LabelEncoder_and_delete_column(test, 'purpose', purpose_le_encoder)  test.drop(['zip_code'], axis=1, inplace=True) encode_with_existing_LabelEncoder(test, 'addr_state', addr_state_le_encoder) encode_with_func(test, 'earliest_cr_line', convert_date) encode_with_existing_LabelEncoder(test, 'initial_list_status', initial_list_status_le_encoder) encode_with_existing_LabelEncoder(test, 'application_type', application_type_le_encoder)  X.dtypes

loan_amnt                              float64 int_rate                               float64 installment                            float64 annual_inc                             float64 dti                                    float64 delinq_2yrs                            float64 inq_last_6mths                         float64 mths_since_last_delinq                 float64 open_acc                               float64 pub_rec                                float64 revol_bal                              float64 revol_util                             float64 total_acc                              float64 collections_12_mths_ex_med             float64 policy_code                            float64 acc_now_delinq                         float64 tot_coll_amt                           float64 tot_cur_bal                            float64 total_rev_hi_lim                       float64 is_title_known                           int64 is_delinq_occurs                         int64 term_le                                  int64 grade_le                                 int64 sub_grade_le                             int64 emp_length=1 year                      float64 emp_length=10+ years                   float64 emp_length=2 years                     float64 emp_length=3 years                     float64 emp_length=4 years                     float64 emp_length=5 years                     float64                                         ...    emp_length=n/a                         float64 home_ownership=ANY                     float64 home_ownership=MORTGAGE                float64 home_ownership=NONE                    float64 home_ownership=OTHER                   float64 home_ownership=OWN                     float64 home_ownership=RENT                    float64 verification_status=Not Verified       float64 verification_status=Source Verified    float64 verification_status=Verified           float64 issue_d_le                             float64 pymnt_plan_le                            int64 purpose=car                            float64 purpose=credit_card                    float64 purpose=debt_consolidation             float64 purpose=educational                    float64 purpose=home_improvement               float64 purpose=house                          float64 purpose=major_purchase                 float64 purpose=medical                        float64 purpose=moving                         float64 purpose=other                          float64 purpose=renewable_energy               float64 purpose=small_business                 float64 purpose=vacation                       float64 purpose=wedding                        float64 addr_state_le                            int64 earliest_cr_line_le                    float64 initial_list_status_le                   int64 application_type_le                      int64 dtype: object

print test.shape

(66730, 65)

Масштабируем признаки, используя ранее созданный scaler:

scaled_test = scaler.transform(test)

Обучаем классификатор с выбранным оптимальным значением С:

clf = LogisticRegression(penalty='l2', C=0.1) clf.fit(scaledX, Y)

LogisticRegression(C=0.1, class_weight=None, dual=False, fit_intercept=True,           intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,           penalty='l2', random_state=None, solver='liblinear', tol=0.0001,           verbose=0, warm_start=False)

Предсказываем:

prediction = clf.predict_proba(scaled_test)[:, 1]

Убедимся, что предсказанные вероятности находятся на отрезке [0,1] и не совпадают между собой (т.е. что модель не получилась константной):

print min(prediction), max(prediction)

0.0 0.999999996487

Преобразуем prediction в DataFrame:

result = pd.DataFrame(np.array(prediction), columns=['prob1'], index=test.index) print result[result['prob1']>0.5].count() print result.count()

prob1    711 dtype: int64 prob1    66730 dtype: int64

Немного странно конечно, что из 67К записей только у 0.7К вероятность больше 0.5

Сохраняем результаты:

result.to_csv('result.csv', encoding='utf8')

Приложение

Код здесь

Судя по информации в Интернете — качество 70-80% для скоринга считается хорошим, но все же есть сомнения, поэтому выслушаю предложения как можно было его улучшить


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