

๋์ถ์ด ์น์ธ๋ ๋์ถ ์ ์ฒญ์์ ์ ์ฉ์ ์, ์๋, ๋์ถ ๊ธ์ก, ์ฉ๋, ์ง์ ๋ฑ ๋ค์ํ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ด๋ค ๋์ถ์ด ์ฐ์ฒด๋๊ฑฐ๋ ๋ถ์คํ ๋ ์ง ์์ธกํ๋ ๋ชจ๋ธ์ ๊ตฌํํ๋ค.

150๊ฐ ๊ฐ๋์ feature์ ๋์ถ ์ ์ฒญ์์ ๋ค์ํ ๋ฐ์ดํฐ๊ฐ ๋ค์ด์๋ค.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from xgboost import XGBClassifier
from sklearn.metrics import classification_report
df = pd.read_csv('C:\\education\\accepted_2007_to_2018Q4.csv', low_memory=False)
display(df)

df1 = df[['loan_amnt', 'term', 'int_rate', 'grade', 'emp_length', 'home_ownership', 'annual_inc', 'purpose', 'dti', 'loan_status']]
df1.shape # (2260701, 10)
์์๋ก ๊ฐ์ฅ ์ฐ๊ด์ฑ ์์ด๋ณด์ด๋ ์ปฌ๋ผ (์ข ์๋ณ์ ํฌํจ) 10๊ฐ๋ฅผ ์ ํํ์๋ค.
df2 = df1.dropna(how='any', inplace=False)
df2.shape # (2113644, 10)
df2.info()
# <class 'pandas.core.frame.DataFrame'>
# Index: 2113644 entries, 0 to 2260698
# Data columns (total 10 columns):
# # Column Dtype
# --- ------ -----
# 0 loan_amnt float64
# 1 term object
# 2 int_rate float64
# 3 grade object
# 4 emp_length object
# 5 home_ownership object
# 6 annual_inc float64
# 7 purpose object
# 8 dti float64
# 9 loan_status object
# dtypes: float64(4), object(6)
# memory usage: 177.4+ MB
๋ฐ์ดํฐ๊ฐ 200๋ง๊ฐ ์ด์ ์ถฉ๋ถํ ์๊ธฐ ๋๋ฌธ์ ์ ์ฒด ๋ฐ์ดํฐ์ ์ต๋ํ ๊ฐ์ ์ ํ์ง ์๋๋ก ์ญ์ ์ฒ๋ฆฌ ํ์๋ค.
df2['term_binary'] = np.where(df2['term'] == ' 36 months', 0, 1)
df3 = df2.drop('term', axis=1, inplace=False)
df3.head()

'36 months', '60 months' ๋๊ฐ์ง ๋ฐ์ดํฐ๋ก ์ด๋ฃจ์ด์ง term ์ปฌ๋ผ์ ํ์ต์ ์ํด 0 ๋๋ 1๋ก ๋ณ๊ฒฝํด์ฃผ์๋ค.
df3_grade_encoded = pd.get_dummies(df3['grade'], prefix='grade').astype(int)
df4 = pd.concat([df3, df3_grade_encoded], axis=1)
df5 = df4.drop('grade', axis=1, inplace=False)
df5.head()

A~G์ ๋ฒ์ฃผํ ๋ฐ์ดํฐ๋ก ์ด๋ฃจ์ด์ง grade ์ปฌ๋ผ์ 0~6์ ์ซ์๋ก ๋ณํํ ์๋ ์์ง๋ง, ์ด๋ฐ ๋ฒ์ฃผ๋ฅผ ๋ํ๋ด๋ ์ซ์๊ฐ์ ์ซ์์ ํฌ๊ธฐ์ ์๋ฏธ๋ฅผ ๊ฐ์ง๋ ๊ฐ์ด ์๋๋ค. ๋ฐ๋ผ์ ์ถํ ์ ๊ทํ๋ฅผ ์ํค๋ฉด ์๋๊ธฐ ๋๋ฌธ์ One-Hot Encoding ์์
์ ํด์ฃผ์๋ค.
df5_grade_encoded = pd.get_dummies(df5['home_ownership'], prefix='home_ownership').astype(int)
df6 = pd.concat([df5, df5_grade_encoded], axis=1)
df7 = df6.drop('home_ownership', axis=1, inplace=False)
df7.head()

home_ownership ์ญ์ One-Hot Encoding ์์
์ ํด์ฃผ์๋ค.
df7_grade_encoded = pd.get_dummies(df7['purpose'], prefix='purpose').astype(int)
df8 = pd.concat([df7, df7_grade_encoded], axis=1)
df9 = df8.drop('purpose', axis=1, inplace=False)
df9.head()

purpose ์ญ์ One-Hot Encoding ์์
์ ํด์ฃผ์๋ค.
def convert_year(x):
if x == '< 1 year':
return 0
elif x == '10+ years':
return 10
else:
return int(x.split()[0])
df9['emp_length_year'] = df9['emp_length'].apply(convert_year)
์ฐ์ ๊ทผ์๊ธฐ๊ฐ์ด ๋ด๊ฒจ์๋ emp_length ์ปฌ๋ผ์ short, medium, long์ผ๋ก ๊ตฌ๊ฐํ ์ฒ๋ฆฌ๋ฅผ ํ๊ธฐ ์ํด ์ฐ์ ์ซ์๋ก ๋ณํํ์ฌ emp_length_year ์ปฌ๋ผ์ ์ ์ฅํด์ฃผ์๋ค.
conditions = [(df9['emp_length_year'] <= 1),
((df9['emp_length_year'] >= 2) & (df9['emp_length_year'] <= 7)),
(df9['emp_length_year'] >= 8)]
choices = ['short', 'medium', 'long']
df9['emp_length_year_binned'] = np.select(conditions, choices)
df9.head()
df9_emp_length_year_binned_encoded = pd.get_dummies(df9['emp_length_year_binned'], prefix='emp_length_year_binned').astype(int)
df10 = pd.concat([df9, df9_emp_length_year_binned_encoded], axis=1)
df11 = df10.drop(['emp_length','emp_length_year','emp_length_year_binned'], axis=1, inplace=False)
df11

์ดํ, ์ซ์๋ก ๋ณํ๋ emp_length_year ์ด์ 1๋ณด๋ค ์์ผ๋ฉด short, 2~7์ medium, 8๋ณด๋ค ํฌ๋ฉด long์ผ๋ก ๊ตฌ๊ฐํ ์ฒ๋ฆฌ ํ, ๋ง์ฐฌ๊ฐ์ง๋ก One-Hot Encoding ์์
์ ํด์ฃผ์๋ค.
df11['loan_status_binary'] = np.where(df11['loan_status'].isin(['Current', 'Fully Paid']), 0, 1)
df12 = df11.drop('loan_status', axis=1, inplace=False)
df12
์ข ์๋ณ์์ธ loan_status์ ๊ฒฝ์ฐ,
Charged Off : ์ฑ๋ฌด ํฌ๊ธฐ
Current : ์ ์์ ์ผ๋ก ์ํ ์ค
Default : ๋์ถ์ํ x
Does not meet the credit policy. Status:Charged Off : ์ ์ฉ์ ์ฑ ์ถฉ์กฑ x, ์ฑ๋ฌด ํฌ๊ธฐ
Does not meet the credit policy. Status:Fully Paid : ์ ์ฉ์ ์ฑ ์ถฉ์กฑ O, ์ ์ก ์ํ
Fully Paid : ์ ์ก ์ํ
In Grace Period : ๋์ถ ์ํ ์ ์
Late (16-30 days) : ๋์ถ ์ํ 16~30์ผ ์ง์ฐ
Late (31-120 days) : ๋์ถ ์ํ 31~120์ผ ์ง์ฐ
์์ ๊ฐ์ด ์ด๋ฃจ์ด์ ธ์๋๋ฐ, Current์ Fully Paid๋ฅผ ์ ์ธํ ๋ค๋ฅธ ์ปฌ๋ผ๋ค์ ๋ชจ๋ 1(๋น์ ์)๋ก ๋ณํํ์๋ค.
print(np.unique(df12['loan_status_binary'], return_counts=True))
# (array([0, 1]), array([1832108, 281536], dtype=int64))
๋ํ ๋ฐ์ดํฐ์ ๋ถ๊ท ํ์ด ๋งค์ฐ ์ฌํ๊ธฐ ๋๋ฌธ์ ๋ชจ๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ๋ฅผ ๋ง์น ํ, SMOTE๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ์ ๋ถ๊ท ํ์ ํด๊ฒฐํด์ผํ๋ค.
fig = plt.figure()
ax1 = fig.add_subplot(1, 4, 1)
ax2 = fig.add_subplot(1, 4, 2)
ax3 = fig.add_subplot(1, 4, 3)
ax4 = fig.add_subplot(1, 4, 4)
ax1.set_title('loan_amnt')
ax2.set_title('int-rate')
ax3.set_title('annual_inc')
ax4.set_title('dti')
ax1.boxplot(df12['loan_amnt'])
ax2.boxplot(df12['int_rate'])
ax3.boxplot(df12['annual_inc'])
ax4.boxplot(df12['dti'])
plt.tight_layout()
plt.show()

์ฐ์ ์ ๋ฐ์ ์ธ ์ด์์น์ ๋ถํฌ๋ฅผ ํ์ธํ๊ธฐ ์ํด boxplot์ผ๋ก ํ์ธํด๋ณด์๋ค.
pd.set_option('display.float_format', '{:.2f}'.format)
df12.describe().iloc[:,0:4]

์ดํ ๋ ์์ธํ ๊ฐ์ ํ์ธํ๊ธฐ ์ํด ๊ธฐ๋ณธ ํต๊ณ๋ฅผ ์ด์ฉํด์ ํ์ธํด๋ณด์๋ค.
df13 = df12[(df12['dti'] >= 0) & (df12['dti'] < 100)]
df13.describe().iloc[:,0:4]

์ฐ์ dti์ ๊ฒฝ์ฐ ๋ถ์ฑ๋น์จ์ด๊ธฐ ๋๋ฌธ์ ๋ฌด์กฐ๊ฑด ์์์ด์ด์ผ ํ๊ณ , ๋น์จ์ด 100์ด ๋๋ ๊ฒ๋ค์ ์ ์ธํ์๋ค.
tmp = df13['annual_inc'].quantile(0.99) # 275000.0
# clip() : upper๋ณด๋ค ๋๋ ๊ฐ์ ๋ชจ๋ upper๋ก ๊ต์ฒด
df13['annual_inc'] = df13['annual_inc'].clip(upper=tmp)
df13.describe().iloc[:,0:4]

annual_inc์ ๊ฒฝ์ฐ ์ฐ๊ฐ ์์
์ด๊ธฐ ๋๋ฌธ์ ์๋ฌด๋ฆฌ ํฌ๋๋ผ๋ ์ค์ ๋ฐ์ดํฐ์ผ ๊ฐ๋ฅ์ฑ์ด ์๋ค. ๋ค์ ๋งํด ์ด์์น๊ฐ ์๋ฏธ๋ฅผ ๊ฐ์ง๊ณ ์์ ์ ์๊ธฐ ๋๋ฌธ์ ์ญ์ ๋ณด๋จ clipping ์์
์ ํตํด ์์ 1% ๋ฐ์ดํฐ๋ก ๋์ฒดํด์ฃผ์๋ค.
df13['annual_inc_log'] = np.log1p(df13['annual_inc'])
df13['dti_log'] = np.log1p(df13['dti'])
df14 = df13.drop(['annual_inc', 'dti'], axis=1, inplace=False)
df14

๋ํ ์ด์์น๊ฐ ๋งค์ฐ ๋ง๊ณ ์ผ๋ฐ์ ์ธ ๋ฐ์ดํฐ์ ๋น์จ์ด ์ ์ annual_inc, dti ์ปฌ๋ผ์ ๋ก๊ทธ๋ณํ์ ํด์ฃผ์๋ค.
x_data = df14.drop('loan_status_binary', axis=1, inplace=False).values
t_data = df14['loan_status_binary'].values
from imblearn.over_sampling import SMOTE
smote = SMOTE(random_state=42)
x_data_resampled, t_data_resampled = smote.fit_resample(x_data, t_data)
np.unique(t_data_resampled, return_counts=True) # (array([0, 1]), array([1830804, 1830804], dtype=int64))
๋ฐ์ดํฐ ์ ์ฒ๋ฆฌ์ ๋ง์ง๋ง์ผ๋ก , ํ์ฌ ์ฐ๋ฆฌ๊ฐ ๊ฐ์ง๊ณ ์๋ ๋ฐ์ดํฐ๋ ๋ถ๊ท ํ์ด ๋งค์ฐ ์ฌํ๋ฐ, ์ด๋ฅผ SMOTE ๊ธฐ๋ฒ์ ์ฌ์ฉํ์ฌ Over Sampling ์์ ์ ํด์ฃผ์๋ค.
scaler = MinMaxScaler()
scaler.fit(x_data_resampled)
x_data_norm = scaler.transform(x_data_resampled)
ํ์ฌ ๋ฐ์ดํฐ๋ ์ด์์น ์ฒ๋ฆฌ๋ ๋ชจ๋ ๋ง์น ์ํ์ด๊ธฐ ๋๋ฌธ์ Min-Max Scaling์ ํตํ ์ ์ฒ๋ฆฌ ์์ ์ ํด์ฃผ์๋ค.
x_data_train_norm, x_data_test_norm, t_data_train, t_data_test = \
train_test_split(x_data_norm,
t_data_resampled,
test_size=0.2,
stratify=t_data_resampled)
ํ์ต์ ์ํด train/test set๋ฅผ ๋ถ๋ฆฌํด์ฃผ์๋ค.
xgbc = XGBClassifier()
xgbc.fit(x_data_train_norm, t_data_train)
result = xgbc.predict(x_data_test_norm)
print(classification_report(t_data_test, result))
# precision recall f1-score support
# 0 0.77 0.84 0.80 366161
# 1 0.82 0.75 0.78 366161
# accuracy 0.79 732322
# macro avg 0.79 0.79 0.79 732322
# weighted avg 0.79 0.79 0.79 732322
ํ๊ฐ ๊ฒฐ๊ณผ ์ต์ข F1 Score๋ 0.79๋ก ๋์ถ๋์๋ค.
precision recall f1-score support
0 0.77 0.84 0.80 366161
1 0.82 0.75 0.78 366161
accuracy 0.79 732322
macro avg 0.79 0.79 0.79 732322
weighted avg 0.79 0.79 0.79 732322
์ด ๋ฌธ์ ๋ ๋ฐ๋ก ๋ฐ์ดํฐ ์ ๋ง ๋ฐ์ ํ์ต์ ์งํํ ๊ฒ์ด๊ธฐ์ ์ ์ถ ์ ์๋ ์๋ค.
๊ธฐ๋ํ ๊ฒ๋ณด๋ค ๋ฎ์ ์ ์๊ฐ ๋์๋ค... Feature Selection์ ์์๋ก ํ๊ณ ๋ชจ๋ธ์ ๋ํ ํ์ดํผ ํ๋ผ๋ฏธํฐ ํ๋๋ ํ์ง์์ ๊ทธ๋ฐ ๊ฒ๊ฐ๋ค. ์ถํ์ ์ด๋ฌํ ์์ ๋ค์ ์ถ๊ฐํด ๋ค์ ํ์ตํด๋ณด์์ผ๊ฒ ๋ค.
https://www.kaggle.com/datasets/wordsforthewise/lending-club