R/ML & DL 공부

[R] 은행 대출 채무 여부 분류 - 의사결정트리

dori_0 2022. 4. 15. 23:50

은행 대출 채무 이행/불이행 예측

C5.0 결정 트리

credit.csv
0.09MB

17개의 변수와 1,000개의 관측치로 이루어진 데이터

default 변수 - yes (채무 불이행), no (채무 이행)

 


1. 데이터 준비 

> credit = read.csv("C:/R/credit.csv", stringsAsFactors = F)
> str(credit)
'data.frame':	1000 obs. of  17 variables:
 $ checking_balance    : chr  "< 0 DM" "1 - 200 DM" "unknown" "< 0 DM" ...
 $ months_loan_duration: int  6 48 12 42 24 36 24 36 12 30 ...
 $ credit_history      : chr  "critical" "good" "critical" "good" ...
 $ purpose             : chr  "furniture/appliances" "furniture/appliances" "education" "furniture/appliances" ...
 $ amount              : int  1169 5951 2096 7882 4870 9055 2835 6948 3059 5234 ...
 $ savings_balance     : chr  "unknown" "< 100 DM" "< 100 DM" "< 100 DM" ...
 $ employment_duration : chr  "> 7 years" "1 - 4 years" "4 - 7 years" "4 - 7 years" ...
 $ percent_of_income   : int  4 2 2 2 3 2 3 2 2 4 ...
 $ years_at_residence  : int  4 2 3 4 4 4 4 2 4 2 ...
 $ age                 : int  67 22 49 45 53 35 53 35 61 28 ...
 $ other_credit        : chr  "none" "none" "none" "none" ...
 $ housing             : chr  "own" "own" "own" "other" ...
 $ existing_loans_count: int  2 1 1 1 2 1 1 1 1 2 ...
 $ job                 : chr  "skilled" "skilled" "unskilled" "skilled" ...
 $ dependents          : int  1 1 2 2 2 2 1 1 1 1 ...
 $ phone               : chr  "yes" "no" "no" "no" ...
 $ default             : chr  "no" "yes" "no" "no" ...
 
> table(credit$default)
 no yes 
700 300
  • 30%가 채무 불이행인 것이 확인된다.

 

 

이 데이터를 훈련 데이터와 테스트 데이터 (9:1)로 나눠보자

신용 데이터를 무작위로 섞은 뒤 나눠주었다.  (잘 섞이게 하기 위해서)

> # 훈련 데이터와 테스트 데이터 생성 
> set.seed(12345)
> credit_rand = credit[order(runif(1000)), ] #1000개 무작위로 섞기
> 
> summary(credit$amount)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    250    1366    2320    3271    3972   18424 
> summary(credit_rand$amount)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    250    1366    2320    3271    3972   18424 
> 
> credit_train = credit_rand[1:900, ]
> credit_test = credit_rand[901:1000, ]
> 
> prop.table(table(credit_train$default))
       no       yes 
0.7022222 0.2977778 

> prop.table(table(credit_test$default))
  no  yes 
0.68 0.32
  • credit과 credit_amount의 summary가 같은 것을 확인하였다.
  • credit_train에는 yes가 29.8%, credit_test에는 32%로 잘 섞인 것을 확인할 수 있다.

 

 

2. 모델 훈련

default를 먼저 factor()로 바꿔주자

> # install.packages("C50")
> library(C50)
> 
> credit_train$default = factor(credit_train$default)
> str(credit_train)
'data.frame':	900 obs. of  17 variables:
 $ checking_balance    : chr  "< 0 DM" "1 - 200 DM" "1 - 200 DM" "< 0 DM" ...
 $ months_loan_duration: int  24 7 12 24 9 18 33 9 20 15 ...
 $ credit_history      : chr  "critical" "good" "good" "good" ...
 $ purpose             : chr  "car" "furniture/appliances" "furniture/appliances" "furniture/appliances" ...
 $ amount              : int  1199 2576 1103 4020 1501 1568 4281 918 2629 1845 ...
 $ savings_balance     : chr  "< 100 DM" "< 100 DM" "< 100 DM" "< 100 DM" ...
 $ employment_duration : chr  "> 7 years" "1 - 4 years" "4 - 7 years" "1 - 4 years" ...
 $ percent_of_income   : int  4 2 4 2 2 3 1 4 2 4 ...
 $ years_at_residence  : int  4 2 3 2 3 4 4 1 3 1 ...
 $ age                 : int  60 35 29 27 34 24 23 30 29 46 ...
 $ other_credit        : chr  "none" "none" "none" "store" ...
 $ housing             : chr  "own" "own" "own" "own" ...
 $ existing_loans_count: int  2 1 2 1 2 1 2 1 2 1 ...
 $ job                 : chr  "unskilled" "skilled" "skilled" "skilled" ...
 $ dependents          : int  1 1 1 1 1 1 1 1 1 1 ...
 $ phone               : chr  "no" "no" "no" "no" ...
 $ default             : Factor w/ 2 levels "no","yes": 2 1 1 1 2 1 2 2 1 1 ...

 

 

이제 모델을 만든뒤 모델에 대해 출력해보면 (길어서 생략)

> # 모델 훈련
> credit_model = C5.0(credit_train[-17], credit_train$default)
> summary(credit_model)

Call:
C5.0.default(x = credit_train[-17], y = credit_train$default)


C5.0 [Release 2.07 GPL Edition]  	Fri Apr 15 23:18:48 2022
-------------------------------

Class specified by attribute `outcome'

Read 900 cases (17 attributes) from undefined.data

Decision tree:

checking_balance = unknown: no (358/44)
checking_balance in {< 0 DM,1 - 200 DM,> 200 DM}:
:...credit_history in {perfect,very good}:
    :...dependents > 1: yes (10/1)
    :   dependents <= 1:
    :   :...savings_balance = < 100 DM: yes (39/11)
    :       savings_balance in {500 - 1000 DM,unknown,> 1000 DM}: no (8/1)
    :       savings_balance = 100 - 500 DM:

                    . . .
                    
                    
# 시각화
# plot(credit_model, compress=T, margin=0.5)
# text(credit_model, cex=1.5)
  • checking_balance가 unknown이면 no로 분류, 위에를 만족하면서 savings_balance가 100 미만이면 yes로 분류 등 각각의 가지에 대해 자세하게 볼 수 있다.
  • 시각화도 해보려고 했는데 너무 복잡해서 나중에 가지치기나 깊이를 지정한 후 다시 해보는 것이 좋을 것 같다.

 

 

이 모델의 성능을 평가해보자

credit_test의 default도 마찬가지로 factor()로 지정해주어야 한다.

> credit_test$default = factor(credit_test$default)
> credit_pred = predict(credit_model, credit_test)
> 
> library(gmodels)
> 
> CrossTable(credit_test$default, credit_pred,
+            prop.chisq = F, prop.c = F,
+            dnn = c('actual default', 'predicted default'))

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  100 

 
               | predicted default 
actual default |        no |       yes | Row Total | 
---------------|-----------|-----------|-----------|
            no |        57 |        11 |        68 | 
               |     0.838 |     0.162 |     0.680 | 
               |     0.570 |     0.110 |           | 
---------------|-----------|-----------|-----------|
           yes |        16 |        16 |        32 | 
               |     0.500 |     0.500 |     0.320 | 
               |     0.160 |     0.160 |           | 
---------------|-----------|-----------|-----------|
  Column Total |        73 |        27 |       100 | 
---------------|-----------|-----------|-----------|
  • CrossTable() 함수를 사용해 예측값과 실제값을 비교하였다.
  • 이 모델의 정확도는 (57+16)/100*100 = 73%이다.
  • 실제 채무 불이행의 분류 오차율이 매우 높기 때문에 모델 성능 향상이 필요해보인다.

 

 

3. 모델 성능 높이기

① Boosting

Boosting 알고리즘은 성능이 낮은 학습기 여러 개를 합하는 기법(Ensemble) 중 하나이다.

trials 매개변수를 추가하여 개별 결증 트리의 수를 정해주면 된다.

> # Boosting
> credit_boost10 = C5.0(credit_train[-17], credit_train$default, trials=20)
> #summary(credit_boost10)
> 
> credit_boost10_pred = predict(credit_boost10, credit_test)
> CrossTable(credit_test$default, credit_boost10_pred,
+            prop.chisq = F, prop.c = F, prop.r = F,
+            dnn = c('actual default', 'predicted default'))

 
   Cell Contents
|-------------------------|
|                       N |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  100 

 
               | predicted default 
actual default |        no |       yes | Row Total | 
---------------|-----------|-----------|-----------|
            no |        59 |         9 |        68 | 
               |     0.590 |     0.090 |           | 
---------------|-----------|-----------|-----------|
           yes |        13 |        19 |        32 | 
               |     0.130 |     0.190 |           | 
---------------|-----------|-----------|-----------|
  Column Total |        72 |        28 |       100 | 
---------------|-----------|-----------|-----------|
  • 20회 반복을 통해 트리의 크기를 줄였다.
  • 이 모델의 정확도는 78%로 오르긴 했지만, 실제 채무 불이행의 분류 오차율은 여전히 40%로 높은 것을 확인할 수 있다.

 

 

② Cost Matrix 사용

채무 불이행을 할 확률이 높은 지원자에게 대출을 해준다면 고비용의 실수가 발생할 위험이 크다

따라서 우리는 false negative의 수를 줄여야 한다.

그러기 위해서 false negative은 4, false positive는 1의 비용을 지불하도록 Cost Matrix를 구성해주자

> error_cost = matrix(c(0, 1, 4, 0), nrow=2)
> error_cost
     [,1] [,2]
[1,]    0    4
[2,]    1    0

 

이제 이를 C5.0 모델의 costs에 적용해주면 된다

> # Cost Matrix
> error_cost = matrix(c(0, 1, 4, 0), nrow=2)
> credit_cost = C5.0(credit_train[-17], credit_train$default, costs=error_cost)
> credit_cost_pred = predict(credit_cost, credit_test)
> CrossTable(credit_test$default, credit_cost_pred,
+            prop.chisq = F, prop.c = F, prop.r = F,
+            dnn = c('actual default', 'predicted default'))

 
   Cell Contents
|-------------------------|
|                       N |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  100 

 
               | predicted default 
actual default |        no |       yes | Row Total | 
---------------|-----------|-----------|-----------|
            no |        42 |        26 |        68 | 
               |     0.420 |     0.260 |           | 
---------------|-----------|-----------|-----------|
           yes |         6 |        26 |        32 | 
               |     0.060 |     0.260 |           | 
---------------|-----------|-----------|-----------|
  Column Total |        48 |        52 |       100 | 
---------------|-----------|-----------|-----------|
  • 이 모델의 정확도는 68%로 전체적으로는 정확도가 떨어졌다.
  • 하지만, 실제 채무 불이행에 대해서는 32개 중 26개를 분류하여 오차율은 약 19%로 낮아진 것을 확인할 수 있다.
  • Cost Matrix를 이용하여 실제 채무 불이행의 분류 오차율을 낮춰보았다.