LIBRARIES

import numpy as np 

import pandas as pd 
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', 99)

import matplotlib.pyplot as plt 
import seaborn as sns 
sns.set()

IMPORTING DATA

data_raw = pd.read_csv('loan_data_2007_2014.csv', index_col=0)
C:\Users\User\AppData\Local\Temp\ipykernel_1224\2969852544.py:1: DtypeWarning: Columns (20) have mixed types. Specify dtype option on import or set low_memory=False.
  data_raw = pd.read_csv('loan_data_2007_2014.csv', index_col=0)

EXPLORING DATA

data_raw.shape
(466285, 74)
data_raw.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 466285 entries, 0 to 466284
Data columns (total 74 columns):
 #   Column                       Non-Null Count   Dtype  
---  ------                       --------------   -----  
 0   id                           466285 non-null  int64  
 1   member_id                    466285 non-null  int64  
 2   loan_amnt                    466285 non-null  int64  
 3   funded_amnt                  466285 non-null  int64  
 4   funded_amnt_inv              466285 non-null  float64
 5   term                         466285 non-null  object 
 6   int_rate                     466285 non-null  float64
 7   installment                  466285 non-null  float64
 8   grade                        466285 non-null  object 
 9   sub_grade                    466285 non-null  object 
 10  emp_title                    438697 non-null  object 
 11  emp_length                   445277 non-null  object 
 12  home_ownership               466285 non-null  object 
 13  annual_inc                   466281 non-null  float64
 14  verification_status          466285 non-null  object 
 15  issue_d                      466285 non-null  object 
 16  loan_status                  466285 non-null  object 
 17  pymnt_plan                   466285 non-null  object 
 18  url                          466285 non-null  object 
 19  desc                         125983 non-null  object 
 20  purpose                      466285 non-null  object 
 21  title                        466265 non-null  object 
 22  zip_code                     466285 non-null  object 
 23  addr_state                   466285 non-null  object 
 24  dti                          466285 non-null  float64
 25  delinq_2yrs                  466256 non-null  float64
 26  earliest_cr_line             466256 non-null  object 
 27  inq_last_6mths               466256 non-null  float64
 28  mths_since_last_delinq       215934 non-null  float64
 29  mths_since_last_record       62638 non-null   float64
 30  open_acc                     466256 non-null  float64
 31  pub_rec                      466256 non-null  float64
 32  revol_bal                    466285 non-null  int64  
 33  revol_util                   465945 non-null  float64
 34  total_acc                    466256 non-null  float64
 35  initial_list_status          466285 non-null  object 
 36  out_prncp                    466285 non-null  float64
 37  out_prncp_inv                466285 non-null  float64
 38  total_pymnt                  466285 non-null  float64
 39  total_pymnt_inv              466285 non-null  float64
 40  total_rec_prncp              466285 non-null  float64
 41  total_rec_int                466285 non-null  float64
 42  total_rec_late_fee           466285 non-null  float64
 43  recoveries                   466285 non-null  float64
 44  collection_recovery_fee      466285 non-null  float64
 45  last_pymnt_d                 465909 non-null  object 
 46  last_pymnt_amnt              466285 non-null  float64
 47  next_pymnt_d                 239071 non-null  object 
 48  last_credit_pull_d           466243 non-null  object 
 49  collections_12_mths_ex_med   466140 non-null  float64
 50  mths_since_last_major_derog  98974 non-null   float64
 51  policy_code                  466285 non-null  int64  
 52  application_type             466285 non-null  object 
 53  annual_inc_joint             0 non-null       float64
 54  dti_joint                    0 non-null       float64
 55  verification_status_joint    0 non-null       float64
 56  acc_now_delinq               466256 non-null  float64
 57  tot_coll_amt                 396009 non-null  float64
 58  tot_cur_bal                  396009 non-null  float64
 59  open_acc_6m                  0 non-null       float64
 60  open_il_6m                   0 non-null       float64
 61  open_il_12m                  0 non-null       float64
 62  open_il_24m                  0 non-null       float64
 63  mths_since_rcnt_il           0 non-null       float64
 64  total_bal_il                 0 non-null       float64
 65  il_util                      0 non-null       float64
 66  open_rv_12m                  0 non-null       float64
 67  open_rv_24m                  0 non-null       float64
 68  max_bal_bc                   0 non-null       float64
 69  all_util                     0 non-null       float64
 70  total_rev_hi_lim             396009 non-null  float64
 71  inq_fi                       0 non-null       float64
 72  total_cu_tl                  0 non-null       float64
 73  inq_last_12m                 0 non-null       float64
dtypes: float64(46), int64(6), object(22)
memory usage: 266.8+ MB
data_raw.sample()
id member_id loan_amnt funded_amnt funded_amnt_inv term int_rate installment grade sub_grade emp_title emp_length home_ownership annual_inc verification_status issue_d loan_status pymnt_plan url desc purpose title zip_code addr_state dti delinq_2yrs earliest_cr_line inq_last_6mths mths_since_last_delinq mths_since_last_record open_acc pub_rec revol_bal revol_util total_acc initial_list_status out_prncp out_prncp_inv total_pymnt total_pymnt_inv total_rec_prncp total_rec_int total_rec_late_fee recoveries collection_recovery_fee last_pymnt_d last_pymnt_amnt next_pymnt_d last_credit_pull_d collections_12_mths_ex_med mths_since_last_major_derog policy_code application_type annual_inc_joint dti_joint verification_status_joint acc_now_delinq tot_coll_amt tot_cur_bal open_acc_6m open_il_6m open_il_12m open_il_24m mths_since_rcnt_il total_bal_il il_util open_rv_12m open_rv_24m max_bal_bc all_util total_rev_hi_lim inq_fi total_cu_tl inq_last_12m
379495 17633586 19766112 7000 7000 7000.0 36 months 13.98 239.18 C C3 Associate 1 year RENT 20000.0 Not Verified Jun-14 Fully Paid n https://www.lendingclub.com/browse/loanDetail…. NaN debt_consolidation Debt consolidation 480xx MI 5.88 0.0 Jan-98 0.0 NaN NaN 9.0 0.0 3581 89.5 21.0 w 0.0 0.0 7923.67 7923.67 7000.0 923.67 0.0 0.0 0.0 Jul-15 5053.51 NaN Jan-16 0.0 NaN 1 INDIVIDUAL NaN NaN NaN 0.0 832.0 28121.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 4000.0 NaN NaN NaN
data_raw.id.nunique()
466285
data_raw.member_id.nunique()
466285

Terlihat bahwa tidak ada id atau member_id yang duplikat, artinya setiap baris sudah mewakili satu individu.

Selanjutnya, pembuangan fitur-fitur yang tidak berguna dilakukan. Contohnya seperti fitur yang merupakan id unik, berupa free text, nilainya kosong semua (NULL), dsb.

cols_to_drop = [
    # unique id
    'id'
    , 'member_id'
    
    # free text
    , 'url'
    , 'desc'
    
    # all null / constant / others
    , 'zip_code' 
    , 'annual_inc_joint'
    , 'dti_joint'
    , 'verification_status_joint'
    , 'open_acc_6m'
    , 'open_il_6m'
    , 'open_il_12m'
    , 'open_il_24m'
    , 'mths_since_rcnt_il'
    , 'total_bal_il'
    , 'il_util'
    , 'open_rv_12m'
    , 'open_rv_24m'
    , 'max_bal_bc'
    , 'all_util'
    , 'inq_fi'
    , 'total_cu_tl'
    , 'inq_last_12m'
    
    # expert judgment
    , 'sub_grade'
]
data = data_raw.drop(cols_to_drop, axis=1)
data.sample(5)
loan_amnt funded_amnt funded_amnt_inv term int_rate installment grade emp_title emp_length home_ownership annual_inc verification_status issue_d loan_status pymnt_plan purpose title addr_state dti delinq_2yrs earliest_cr_line inq_last_6mths mths_since_last_delinq mths_since_last_record open_acc pub_rec revol_bal revol_util total_acc initial_list_status out_prncp out_prncp_inv total_pymnt total_pymnt_inv total_rec_prncp total_rec_int total_rec_late_fee recoveries collection_recovery_fee last_pymnt_d last_pymnt_amnt next_pymnt_d last_credit_pull_d collections_12_mths_ex_med mths_since_last_major_derog policy_code application_type acc_now_delinq tot_coll_amt tot_cur_bal total_rev_hi_lim
372611 19200 19200 19200.0 36 months 12.99 646.84 C Sr Mgr. WW Field Services 6 years MORTGAGE 169000.0 Source Verified Jun-14 Current n debt_consolidation Debt consolidation TX 14.69 0.0 Sep-82 0.0 NaN NaN 16.0 0.0 50835 73.4 21.0 w 9994.30 9994.30 12289.96000 12289.96 9205.70 3084.26 0.0 0.0 0.0 Jan-16 646.84 Feb-16 Jan-16 0.0 NaN 1 INDIVIDUAL 0.0 0.0 324434.0 69300.0
59649 5000 5000 5000.0 36 months 15.61 174.83 C Admin Assist II 10+ years OWN 33000.0 Not Verified Nov-13 Current n debt_consolidation Debt consolidation TX 26.80 0.0 Dec-03 2.0 44.0 NaN 8.0 0.0 5667 48.9 19.0 f 1781.36 1781.36 4369.99000 4369.99 3218.64 1151.35 0.0 0.0 0.0 Jan-16 174.83 Feb-16 Jan-16 0.0 47.0 1 INDIVIDUAL 0.0 75.0 24965.0 11600.0
292727 5000 5000 5000.0 36 months 10.15 161.69 B employment program representative 10+ years MORTGAGE 90000.0 Verified Oct-14 Late (16-30 days) n credit_card Credit card refinancing CA 7.87 2.0 Oct-87 1.0 5.0 NaN 11.0 0.0 4408 23.4 53.0 w 3233.34 3233.34 2263.66000 2263.66 1766.66 497.00 0.0 0.0 0.0 Jan-16 161.69 Feb-16 Jan-16 0.0 5.0 1 INDIVIDUAL 0.0 0.0 159715.0 18800.0
291676 10000 10000 10000.0 36 months 8.39 315.17 A Professor 8 years RENT 120000.0 Not Verified Oct-14 Current n debt_consolidation Debt consolidation OH 7.22 0.0 Jul-04 1.0 NaN NaN 5.0 0.0 6054 36.0 12.0 w 6135.58 6135.58 4727.55000 4727.55 3864.42 863.13 0.0 0.0 0.0 Jan-16 315.17 Feb-16 Jan-16 0.0 NaN 1 INDIVIDUAL 0.0 0.0 36663.0 16800.0
164566 12000 12000 12000.0 36 months 13.11 404.97 B Costco 5 years MORTGAGE 61000.0 Not Verified Feb-13 Fully Paid n debt_consolidation refi lend.club loan +1 Credit Card CO 8.99 0.0 Jan-00 0.0 41.0 NaN 6.0 0.0 5234 61.6 14.0 f 0.00 0.00 14472.28613 14472.29 12000.00 2472.29 0.0 0.0 0.0 Jul-15 3141.17 NaN Jan-16 0.0 NaN 1 INDIVIDUAL 0.0 360.0 196803.0 8500.0

DEFINE TARGET VARIABLE / LABELING

Dalam project credit risk modeling, tujuan utama adalah untuk melakukan prediksi terhadap suatu individu akan kemampuan mereka untuk melakukan pembayaran terhadap pinjaman/kredit yang diberikan. Oleh karena itu, variabel target yang digunakan harus mencerminkan kemampuan individu dalam hal tersebut.

Dalam dataset ini, variabel loan_status adalah variabel yang dapat dijadikan variabel target karena mencerminkan performa masing-masing individu dalam melakukan pembayaran terhadap pinjaman/kredit selama ini.

data.loan_status.value_counts(normalize=True)*100
Current                                                48.087757
Fully Paid                                             39.619332
Charged Off                                             9.109236
Late (31-120 days)                                      1.479782
In Grace Period                                         0.674695
Does not meet the credit policy. Status:Fully Paid      0.426349
Late (16-30 days)                                       0.261214
Default                                                 0.178432
Does not meet the credit policy. Status:Charged Off     0.163205
Name: loan_status, dtype: float64

Dapat dilihat bahwa variabel loan_status memiliki beberapa nilai:

Current artinya pembayaran lancar; Charged Off artinya pembayaran macet sehingga dihapusbukukan; Late artinya pembayaran telat dilakukan; In Grace Period artinya dalam masa tenggang; Fully Paid artinya pembayaran lunas; Default artinya pembayaran macet

Dari definisi-definisi tersebut, masing-masing individu dapat ditandai apakah mereka merupakan bad loan (peminjam yang buruk) atau good loan (peminjam yang baik)

Definisi bad dan good loan terkadang bisa berbeda tergantung dari kebutuhan bisnis. Pada contoh ini, saya menggunakan keterlambatan pembayaran di atas 30 hari dan yang lebih buruk dari itu sebagai penanda bad loan.

bad_status = [
    'Charged Off' 
    , 'Default' 
    , 'Does not meet the credit policy. Status:Charged Off'
    , 'Late (31-120 days)'
]

data['bad_flag'] = np.where(data['loan_status'].isin(bad_status), 1, 0)
data['bad_flag'].value_counts(normalize=True)*100
0    89.069346
1    10.930654
Name: bad_flag, dtype: float64

Setelah melakukan flagging terhadap bad/good loan, dapat dilihat bahwa jumlah individu yang ditandai sebagai bad loan jauh lebih sedikit daripada good loan. Hal ini menyebabkan problem ini menjadi problem imbalanced dataset.

Jangan lupa untuk membuang kolom asal loan_status

data.drop('loan_status', axis=1, inplace=True)

CLEANING, PREPROCESSING, FEATURE ENGINEERING

Pada step ini, dilakukan pembersihan/modifikasi beberapa fitur ke dalam format yang dapat digunakan untuk modeling.

emp_length

Memodifikasi emp_length. Contoh: 4 years -> 4

data['emp_length'].unique()
array(['10+ years', '< 1 year', '1 year', '3 years', '8 years', '9 years',
       '4 years', '5 years', '6 years', '2 years', '7 years', nan],
      dtype=object)
data['emp_length_int'] = data['emp_length'].str.replace('\+ years', '')
data['emp_length_int'] = data['emp_length_int'].str.replace('< 1 year', str(0))
data['emp_length_int'] = data['emp_length_int'].str.replace(' years', '')
data['emp_length_int'] = data['emp_length_int'].str.replace(' year', '')
C:\Users\User\AppData\Local\Temp\ipykernel_1224\1742887160.py:1: FutureWarning: The default value of regex will change from True to False in a future version.
  data['emp_length_int'] = data['emp_length'].str.replace('\+ years', '')
data['emp_length_int'] = data['emp_length_int'].astype(float)
data.drop('emp_length', axis=1, inplace=True)

term

Memodifikasi term. Contoh: 36 months -> 36

data['term'].unique()
data['term_int'] = data['term'].str.replace(' months', '')
data['term_int'] = data['term_int'].astype(float)
data.drop('term', axis=1, inplace=True)

earliest_cr_line

Memodifikasi earliest_cr_line dari format bulan-tahun menjadi perhitungan berapa lama waktu berlalu sejak waktu tersebut. Untuk melakukan hal ini, umumnya digunakan reference date = hari ini. Namun, karena dataset ini merupakan dataset tahun 2007-2014, maka akan lebih relevan jika menggunakan reference date di sekitar tahun 2017. Dalam contoh ini, saya menggunakan tanggal 2017-12-01 sebagai reference date.

data['earliest_cr_line'].head(3)
data['earliest_cr_line_date'] = pd.to_datetime(data['earliest_cr_line'], format='%b-%y')
data['earliest_cr_line_date'].head(3)
data['mths_since_earliest_cr_line'] = round(pd.to_numeric((pd.to_datetime('2017-12-01') - data['earliest_cr_line_date']) / np.timedelta64(1, 'M')))
data['mths_since_earliest_cr_line'].head(3)
data['mths_since_earliest_cr_line'].describe()

Terlihat ada nilai yang aneh, yaitu negatif.

data[data['mths_since_earliest_cr_line']<0][['earliest_cr_line', 'earliest_cr_line_date', 'mths_since_earliest_cr_line']].head(3)

Ternyata nilai negatif muncul karena fungsi Python salah menginterpretasikan tahun 62 menjadi tahun 2062, padahal seharusnya merupakan tahun 1962.

Untuk mengatasi hal ini, dapat dilakukan preprocessing lebih jauh jika ingin membenarkan tahun 2062 menjadi 1962. Namun, kali ini saya hanya mengubah nilai yang negatif menjadi nilai maximum dari fitur tersebut. Karena di sini saya mengetahui bahwa nilai-nilai yang negatif artinya adalah data yang sudah tua (tahun 1900an), maka masih masuk akal jika saya mengganti nilai-nilai tersebut menjadi nilai terbesar.

data.loc[data['mths_since_earliest_cr_line']<0, 'mths_since_earliest_cr_line'] = data['mths_since_earliest_cr_line'].max()
data.drop(['earliest_cr_line', 'earliest_cr_line_date'], axis=1, inplace=True)

issue_d

Konsep preprocessing yang dilakukan sama dengan yang dilakukan terhadap variabel earliest_cr_line

data['issue_d_date'] = pd.to_datetime(data['issue_d'], format='%b-%y')
data['mths_since_issue_d'] = round(pd.to_numeric((pd.to_datetime('2017-12-01') - data['issue_d_date']) / np.timedelta64(1, 'M')))
data['mths_since_issue_d'].describe()
data.drop(['issue_d', 'issue_d_date'], axis=1, inplace=True)

last_pymnt_d

Konsep preprocessing yang dilakukan sama dengan yang dilakukan terhadap variabel earliest_cr_line

data['last_pymnt_d_date'] = pd.to_datetime(data['last_pymnt_d'], format='%b-%y')
data['mths_since_last_pymnt_d'] = round(pd.to_numeric((pd.to_datetime('2017-12-01') - data['last_pymnt_d_date']) / np.timedelta64(1, 'M')))
data['mths_since_last_pymnt_d'].describe()
data.drop(['last_pymnt_d', 'last_pymnt_d_date'], axis=1, inplace=True)

next_pymnt_d

Konsep preprocessing yang dilakukan sama dengan yang dilakukan terhadap variabel earliest_cr_line

data['next_pymnt_d_date'] = pd.to_datetime(data['next_pymnt_d'], format='%b-%y')
data['mths_since_next_pymnt_d'] = round(pd.to_numeric((pd.to_datetime('2017-12-01') - data['next_pymnt_d_date']) / np.timedelta64(1, 'M')))
data['mths_since_next_pymnt_d'].describe()
data.drop(['next_pymnt_d', 'next_pymnt_d_date'], axis=1, inplace=True)

last_credit_pull_d

Konsep preprocessing yang dilakukan sama dengan yang dilakukan terhadap variabel earliest_cr_line

data['last_credit_pull_d_date'] = pd.to_datetime(data['last_credit_pull_d'], format='%b-%y')
data['mths_since_last_credit_pull_d'] = round(pd.to_numeric((pd.to_datetime('2017-12-01') - data['last_credit_pull_d_date']) / np.timedelta64(1, 'M')))
data['mths_since_last_credit_pull_d'].describe()
data.drop(['last_credit_pull_d', 'last_credit_pull_d_date'], axis=1, inplace=True)

EXPLORATORY DATA ANALYSIS

Correlation Check

plt.figure(figsize=(20,20))
sns.heatmap(data.corr())

Di sini, jika ada pasangan fitur-fitur yang memiliki korelasi tinggi maka akan diambil salah satu saja. Nilai korelasi yang dijadikan patokan sebagai korelasi tinggi tidak pasti, umumnya digunakan angka 0.7.

corr_matrix = data.corr().abs()
upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(np.bool))
to_drop_hicorr = [column for column in upper.columns if any(upper[column] > 0.7)]
to_drop_hicorr
data.drop(to_drop_hicorr, axis=1, inplace=True)

Check Categorical Features

data.select_dtypes(include='object').nunique()

Pada tahap ini dilakukan pembuangan fitur yang memiliki nilai unik yang sangat tinggi (high cardinality) dan fitur yang hanya memiliki satu nilai unik saja.

data.drop(['emp_title', 'title', 'application_type'], axis=1, inplace=True)
data.select_dtypes(exclude='object').nunique()

Ternyata, pada tipe data selain object juga terdapat fitur yang hanya memiliki satu nilai unik saja, maka akan ikut dibuang juga.

data.drop(['policy_code'], axis=1, inplace=True)
for col in data.select_dtypes(include='object').columns.tolist():
    print(data[col].value_counts(normalize=True)*100)
    print('\n')

Fitur yang sangat didominasi oleh salah satu nilai saja akan dibuang pada tahap ini.

data.drop('pymnt_plan', axis=1, inplace=True)

MISSING VALUES

Missing Value Checking

check_missing = data.isnull().sum() * 100 / data.shape[0]
check_missing[check_missing > 0].sort_values(ascending=False)

Di sini, kolom-kolom dengan missing values di atas 75% dibuang

data.drop('mths_since_last_record', axis=1, inplace=True)

Missing Values Filling

data['annual_inc'].fillna(data['annual_inc'].mean(), inplace=True)
data['mths_since_earliest_cr_line'].fillna(0, inplace=True)
data['acc_now_delinq'].fillna(0, inplace=True)
data['total_acc'].fillna(0, inplace=True)
data['pub_rec'].fillna(0, inplace=True)
data['open_acc'].fillna(0, inplace=True)
data['inq_last_6mths'].fillna(0, inplace=True)
data['delinq_2yrs'].fillna(0, inplace=True)
data['collections_12_mths_ex_med'].fillna(0, inplace=True)
data['revol_util'].fillna(0, inplace=True)
data['emp_length_int'].fillna(0, inplace=True)
data['tot_cur_bal'].fillna(0, inplace=True)
data['tot_coll_amt'].fillna(0, inplace=True)
data['mths_since_last_delinq'].fillna(-1, inplace=True)

FEATURE SCALING AND TRANSFORMATION

One Hot Encoding

Semua kolom kategorikal dilakukan One Hot Encoding.

categorical_cols = [col for col in data.select_dtypes(include='object').columns.tolist()]
onehot = pd.get_dummies(data[categorical_cols], drop_first=True)
onehot.head()

Standardization

Semua kolom numerikal dilakukan proses standarisasi dengan StandardScaler.

numerical_cols = [col for col in data.columns.tolist() if col not in categorical_cols + ['bad_flag']]
from sklearn.preprocessing import StandardScaler

ss = StandardScaler()
std = pd.DataFrame(ss.fit_transform(data[numerical_cols]), columns=numerical_cols)
std.head()

Transformed Dataframe

Menggabungkan kembali kolom-kolom hasil transformasi

data_model = pd.concat([onehot, std, data[['bad_flag']]], axis=1)

MODELING

Train-Test Split

from sklearn.model_selection import train_test_split
X = data_model.drop('bad_flag', axis=1)
y = data_model['bad_flag']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
X_train.shape, X_test.shape

Training

Pada contoh ini digunakan algoritma Random Forest untuk pemodelan.

from sklearn.ensemble import RandomForestClassifier
rfc = RandomForestClassifier(max_depth=4)
rfc.fit(X_train, y_train)

Feature Importance dapat ditampilkan.

arr_feature_importances = rfc.feature_importances_
arr_feature_names = X_train.columns.values
    
df_feature_importance = pd.DataFrame(index=range(len(arr_feature_importances)), columns=['feature', 'importance'])
df_feature_importance['feature'] = arr_feature_names
df_feature_importance['importance'] = arr_feature_importances
df_all_features = df_feature_importance.sort_values(by='importance', ascending=False)
df_all_features

Validation

Untuk mengukur performa model, dua metrik yang umum dipakai dalam dunia credit risk adalah AUC dan KS.

y_pred_proba = rfc.predict_proba(X_test)[:][:,1]

df_actual_predicted = pd.concat([pd.DataFrame(np.array(y_test), columns=['y_actual']), pd.DataFrame(y_pred_proba, columns=['y_pred_proba'])], axis=1)
df_actual_predicted.index = y_test.index

AUC

from sklearn.metrics import roc_curve, roc_auc_score
fpr, tpr, tr = roc_curve(df_actual_predicted['y_actual'], df_actual_predicted['y_pred_proba'])
auc = roc_auc_score(df_actual_predicted['y_actual'], df_actual_predicted['y_pred_proba'])

plt.plot(fpr, tpr, label='AUC = %0.4f' %auc)
plt.plot(fpr, fpr, linestyle = '--', color='k')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend()

KS

df_actual_predicted = df_actual_predicted.sort_values('y_pred_proba')
df_actual_predicted = df_actual_predicted.reset_index()

df_actual_predicted['Cumulative N Population'] = df_actual_predicted.index + 1
df_actual_predicted['Cumulative N Bad'] = df_actual_predicted['y_actual'].cumsum()
df_actual_predicted['Cumulative N Good'] = df_actual_predicted['Cumulative N Population'] - df_actual_predicted['Cumulative N Bad']
df_actual_predicted['Cumulative Perc Population'] = df_actual_predicted['Cumulative N Population'] / df_actual_predicted.shape[0]
df_actual_predicted['Cumulative Perc Bad'] = df_actual_predicted['Cumulative N Bad'] / df_actual_predicted['y_actual'].sum()
df_actual_predicted['Cumulative Perc Good'] = df_actual_predicted['Cumulative N Good'] / (df_actual_predicted.shape[0] - df_actual_predicted['y_actual'].sum())
df_actual_predicted.head()
KS = max(df_actual_predicted['Cumulative Perc Good'] - df_actual_predicted['Cumulative Perc Bad'])

plt.plot(df_actual_predicted['y_pred_proba'], df_actual_predicted['Cumulative Perc Bad'], color='r')
plt.plot(df_actual_predicted['y_pred_proba'], df_actual_predicted['Cumulative Perc Good'], color='b')
plt.xlabel('Estimated Probability for Being Bad')
plt.ylabel('Cumulative %')
plt.title('Kolmogorov-Smirnov:  %0.4f' %KS)

Model yang dibangun menghasilkan performa AUC = 0.857 dan KS = 0.56. Pada dunia credit risk modeling, umumnya AUC di atas 0.7 dan KS di atas 0.3 sudah termasuk performa yang baik.

SARAN

Beberapa hal lain yang dapat dilakukan untuk project ini: