rm(list = ls())
library(tidyverse)
library(data.table)
setwd("D:/Rcode/RPub/BankingData")
hmeq <- fread("hmeq.csv")

Giải thích dữ liệu hmeq. HMEQ là bộ dữ liệu về các khoản vay có tài sản đảm bảo là nhà. Dữ liệu bao gồm 5960 khoản vay được phân loại thành các nhóm vỡ nợ hoặc không vỡ nợ.

knn chỉ tính khoảng cách Euclician của những tọa độ số học nên với các biến factor ta phải chuẩn hóa về dạng số học.

#Them bien STT
hmeq$STT <- 1:nrow(hmeq)
#Tim cac bien factor
str(hmeq)
## Classes 'data.table' and 'data.frame':   5960 obs. of  14 variables:
##  $ BAD    : int  1 1 1 1 0 1 1 1 1 1 ...
##  $ LOAN   : int  1100 1300 1500 1500 1700 1700 1800 1800 2000 2000 ...
##  $ MORTDUE: num  25860 70053 13500 NA 97800 ...
##  $ VALUE  : num  39025 68400 16700 NA 112000 ...
##  $ REASON : chr  "HomeImp" "HomeImp" "HomeImp" "" ...
##  $ JOB    : chr  "Other" "Other" "Other" "" ...
##  $ YOJ    : num  10.5 7 4 NA 3 9 5 11 3 16 ...
##  $ DEROG  : int  0 0 0 NA 0 0 3 0 0 0 ...
##  $ DELINQ : int  0 2 0 NA 0 0 2 0 2 0 ...
##  $ CLAGE  : num  94.4 121.8 149.5 NA 93.3 ...
##  $ NINQ   : int  1 0 1 NA 0 1 1 0 1 0 ...
##  $ CLNO   : int  9 14 10 NA 14 8 17 8 12 13 ...
##  $ DEBTINC: num  NA NA NA NA NA ...
##  $ STT    : int  1 2 3 4 5 6 7 8 9 10 ...
##  - attr(*, ".internal.selfref")=<externalptr>
#Co 2 bien numeric la REASON va JOB
#Cac gia tri factor cua REASON
hmeq %>% distinct(REASON)
##     REASON
## 1: HomeImp
## 2:        
## 3: DebtCon
#Tao bien REASON.LV
#Thong ke tan xuat REASON doi voi no xau
hmeq %>% filter(BAD == 1) %>% group_by(BAD,REASON) %>% 
  summarise(No_Loan = n()) %>% 
  arrange(No_Loan)
## Source: local data frame [3 x 3]
## Groups: BAD [1]
## 
## # A tibble: 3 x 3
##     BAD REASON  No_Loan
##   <int> <chr>     <int>
## 1     1 ""           48
## 2     1 HomeImp     396
## 3     1 DebtCon     745
#Tao bien REASON.LV gan gia tri lon hon doi voi cong viec co ty le no xau cao hon
hmeq <- hmeq %>% mutate(REASON.LV = case_when(
               .$REASON == '' ~ 1,
               .$REASON == 'HomeImp' ~ 2,
               .$REASON == 'DebtCon' ~ 3
                )) 


#Cac gia tri factor cua JOB
hmeq %>% distinct(JOB)
##       JOB
## 1   Other
## 2        
## 3  Office
## 4   Sales
## 5     Mgr
## 6 ProfExe
## 7    Self
#Thong ke tan suat JOB doi voi no xau
hmeq %>% filter(BAD == 1) %>% group_by(BAD,JOB) %>% 
  summarise(No_Loan = n()) %>% 
  arrange(No_Loan) 
## Source: local data frame [7 x 3]
## Groups: BAD [1]
## 
## # A tibble: 7 x 3
##     BAD JOB     No_Loan
##   <int> <chr>     <int>
## 1     1 ""           23
## 2     1 Sales        38
## 3     1 Self         58
## 4     1 Office      125
## 5     1 Mgr         179
## 6     1 ProfExe     212
## 7     1 Other       554
#Tao bien JOB.LV gan gia tri lon hon doi voi cong viec co ty le no xau cao hon
hmeq <- hmeq %>% mutate(JOB.LV = case_when(
                .$JOB == '' ~ 1,
                .$JOB == 'Sales' ~ 2,
                .$JOB == 'Self' ~ 3,
                .$JOB == 'Office' ~ 4,
                .$JOB == 'Mgr' ~ 5,
                .$JOB == 'ProExe' ~ 6,
                .$JOB == 'Other' ~ 7))

hmeq %>% distinct(JOB.LV)
##   JOB.LV
## 1      7
## 2      1
## 3      4
## 4      2
## 5      5
## 6     NA
## 7      3

Phân chia tập train và test:

library(gmodels)
set.seed(1)
#Loai bo cac bien characters
hmeq <- hmeq %>% select(-REASON, -JOB)
#Xy ly missing value
hmeq[is.na(hmeq)] <- 0
hmeq_bad <- hmeq %>% filter(BAD == 1) 
hmeq_good <- setdiff(hmeq,hmeq_bad)
#Them STT cho BAD
#hmeq_bad$BADSTT <- 1:nrow(hmeq_bad)
#Chia tap train/test cua BAD theo ty le 0.8
training_bad <- hmeq_bad %>% sample_n(nrow(hmeq_bad) * 0.8) 
testing_bad <- setdiff(hmeq_bad,training_bad)
#Chia tap train/test cua GOOD theo ty le 0.8
training_good <- hmeq_good %>% sample_n(nrow(hmeq_good)*0.8)
testing_good <- setdiff(hmeq_good,training_good)
#Tao tap train/test
training <- rbind(training_bad,training_good)
testing <- rbind(testing_bad, testing_good)

CrossTable(training$BAD)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  4767 
## 
##  
##           |         0 |         1 | 
##           |-----------|-----------|
##           |      3816 |       951 | 
##           |     0.801 |     0.199 | 
##           |-----------|-----------|
## 
## 
## 
## 
CrossTable(testing$BAD)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  1193 
## 
##  
##           |         0 |         1 | 
##           |-----------|-----------|
##           |       955 |       238 | 
##           |     0.801 |     0.199 | 
##           |-----------|-----------|
## 
## 
## 
## 

Xây dựng model dự báo bằng phương pháp KNN, kích thước mẫu voting được lựa chọn là 10.

library(class)
set.seed(1)
#Lay factor nhan loai no cua tap traing
cl_training <- as.factor(training$BAD)

knn <- knn(train = select(training, -BAD,-STT),test = select(testing, -BAD,-STT), cl = cl_training,k=10)
# giá trị dự báo trên tập test:
head(knn,10)
##  [1] 1 0 0 0 1 1 0 0 0 1
## Levels: 0 1

So sánh kết quả dự báo trên tập test với giá trị thật

library(gmodels)
CrossTable(knn, testing$BAD)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## | Chi-square contribution |
## |           N / Row Total |
## |           N / Col Total |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  1193 
## 
##  
##              | testing$BAD 
##          knn |         0 |         1 | Row Total | 
## -------------|-----------|-----------|-----------|
##            0 |       943 |       186 |      1129 | 
##              |     1.703 |     6.834 |           | 
##              |     0.835 |     0.165 |     0.946 | 
##              |     0.987 |     0.782 |           | 
##              |     0.790 |     0.156 |           | 
## -------------|-----------|-----------|-----------|
##            1 |        12 |        52 |        64 | 
##              |    30.043 |   120.550 |           | 
##              |     0.188 |     0.812 |     0.054 | 
##              |     0.013 |     0.218 |           | 
##              |     0.010 |     0.044 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |       955 |       238 |      1193 | 
##              |     0.801 |     0.199 |           | 
## -------------|-----------|-----------|-----------|
## 
## 

Bảng trên có nội dung của 1 ô lần lượt từ trên xuống dưới là:

library("kableExtra")
library("knitr")

Static_Index <- c('N','Chi-square contribution ','N / Row Total','N / Col Total',
                  'N / Table Total')
Meaning <- c('So quan sat theo dac tinh (Actual, Predict)','Gia tri Chi-square','Ty le dong','Ty le cot','Ty le so voi tong the')

text.table <- data.frame(Static_Index,Meaning)

kable(text.table,"html") %>% 
  kable_styling("striped", full_width = F) %>%
  add_indent(c(1, 3, 5))
Static_Index Meaning
N So quan sat theo dac tinh (Actual, Predict)
Chi-square contribution Gia tri Chi-square
N / Row Total Ty le dong
N / Col Total Ty le cot
N / Table Total Ty le so voi tong the

Từ đó ta có thể thấy xác xuất dự báo đúng một hợp đồng không nợ xấu là 98.7%. Tỷ lệ dự báo đúng 1 hợp đồng nợ xấu là 21.8%. Tỷ lệ dự báo sai hợp đồng không nợ xấu thành nợ xấu là 1.3% và tỷ lệ dự báo sai hợp đồng nợ xấu thành không nợ xấu (đây là rủi ro lớn nhất đối với bank) là 78.2%. Tỷ lệ dự báo đúng 1 hợp đồng nợ xấu cao hơn so với xác xuất ngẫu nhiên một hợp đồng là nợ xấu là 19.9%. Tuy nhiên đối với ngân hàng, mức độ dự báo chính xác được hợp đồng nợ xấu như vậy còn tương đối thấp. Ta thử điều chỉnh kích thước tập voting để xem model có sự cải thiện hay không?

#Tao vong lap dieu chinh kich thuoc voting size
result <- data.frame()
df <- data.frame()
for (i in 1:25) {
  knn <- knn(train = select(training, -BAD, -STT),test = select(testing, -BAD, -STT), cl = cl_training,k=i)
  df <- data.frame(voting_size = i, knn)
  result <- rbind(df,result)
}


#Tao vong lap luu label BAD cua testing ung voi moi vong lap
actual <- data.frame()
for (i in 1:25) {
  df <- data.frame(voting_size = i,BAD = testing[,1])
  actual <- rbind(df,actual)
}

#Merge gia tri actual va gia tri forecast cua knn ung voi voting_size
result$actual <- actual$BAD

Hiển thị các kết quả tần xuất dự báo chính xác trên tập test

#Dự báo chính xác trường hợp nợ xấu
result %>% group_by(voting_size, actual, knn) %>%
  summarise(No_Loan = n()) %>% 
  filter(actual == 1, knn == 1) %>% 
  arrange(desc(No_Loan))
## Source: local data frame [25 x 4]
## Groups: voting_size, actual [25]
## 
## # A tibble: 25 x 4
##    voting_size actual knn   No_Loan
##          <int>  <int> <fct>   <int>
##  1           2      1 1         101
##  2           1      1 1          93
##  3           3      1 1          70
##  4           4      1 1          67
##  5           5      1 1          65
##  6           6      1 1          65
##  7           8      1 1          61
##  8           7      1 1          57
##  9           9      1 1          57
## 10          10      1 1          52
## # ... with 15 more rows
#Dự báo chính xác trường hợp không nợ xấu
result %>% group_by(voting_size, actual, knn) %>%
  summarise(No_Loan = n()) %>% 
  filter(actual == 0, knn == 0) %>% 
  arrange(desc(No_Loan))
## Source: local data frame [25 x 4]
## Groups: voting_size, actual [25]
## 
## # A tibble: 25 x 4
##    voting_size actual knn   No_Loan
##          <int>  <int> <fct>   <int>
##  1          21      0 0         949
##  2          25      0 0         949
##  3          17      0 0         948
##  4          19      0 0         948
##  5          22      0 0         948
##  6          23      0 0         948
##  7          24      0 0         948
##  8          16      0 0         947
##  9          18      0 0         947
## 10          20      0 0         947
## # ... with 15 more rows

Luôn có sự đánh đổi giữa dự báo chính xác trường hợp nợ xấu và dự báo chính xác trường hợp không nợ xấu. Trong từng trườn hợp của voting_size thì khi tỷ lệ dự báo chính xác nợ xấu cao sẽ tương ứng với lệ dự báo chính xác không nợ xấu thấp và ngược lại. Muốn ưu tiên dự báo chính xác nợ xấu ta sẽ lựa chọn voting_size thấp và muốn ưu tiên dự báo chính xác trường hợp không nợ xấu ta sẽ nâng cao voting_size.

Tuy nhiên chúng ta nhận thấy một điểm yếu của knn đó là sự khác biệt về độ đo của các biến số sẽ ảnh hưởng đến kết quả dự báo khi mà các biến có số đo lớn sẽ có tác động mạnh tới khoảng cách euclician. Do đó nếu chuẩn hóa độ đo liệu kết quả dự báo của model có cải thiện hay không? Ta sẽ thử nghiệm như bên dưới.

Xây dựng hàm chuẩn hóa dữ liệu:

#Ham chuan hoa 1
chuanhoa_min_max <- function(x){
  (x-min(x))/(max(x)-mean(x))
}
#Ham chuan hoa 2
chuanhoa_std <- function(x){
  (x-mean(x))/sd(x)
}

Áp dụng các hàm chuẩn hóa trên cho tập dữ liệu

#Chuan hoa tren tap du lieu hmeq
hmeq_chuanhoa <- as.data.frame(lapply(select(hmeq,-BAD,-STT),chuanhoa_min_max)) %>%   data.frame(BAD=hmeq$BAD,.,STT=hmeq$STT)
#Lay tap training
training_chuanhoa <- hmeq_chuanhoa %>% filter(STT %in% training$STT)
#Lay tap testing
testing_chuanhoa <- hmeq_chuanhoa %>% filter(STT %in% testing$STT)
#Xay dung model knn

Xây dựng model knn với voting size = 10

library(class)
set.seed(1)
#Lay factor nhan loai no cua tap traing
cl_training_chuanhoa <- as.factor(training_chuanhoa$BAD)

knn <- knn(train = select(training_chuanhoa, -BAD,-STT),test = select(testing_chuanhoa, -BAD,-STT), cl = cl_training,k=10)
# giá trị dự báo trên tập test:
head(knn,10)
##  [1] 1 1 1 0 1 1 1 0 1 0
## Levels: 0 1
#so sanh ket qua du bao tren tap test
CrossTable(knn,testing_chuanhoa$BAD)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## | Chi-square contribution |
## |           N / Row Total |
## |           N / Col Total |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  1193 
## 
##  
##              | testing_chuanhoa$BAD 
##          knn |         0 |         1 | Row Total | 
## -------------|-----------|-----------|-----------|
##            0 |       822 |       191 |      1013 | 
##              |     0.152 |     0.609 |           | 
##              |     0.811 |     0.189 |     0.849 | 
##              |     0.861 |     0.803 |           | 
##              |     0.689 |     0.160 |           | 
## -------------|-----------|-----------|-----------|
##            1 |       133 |        47 |       180 | 
##              |     0.854 |     3.425 |           | 
##              |     0.739 |     0.261 |     0.151 | 
##              |     0.139 |     0.197 |           | 
##              |     0.111 |     0.039 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |       955 |       238 |      1193 | 
##              |     0.801 |     0.199 |           | 
## -------------|-----------|-----------|-----------|
## 
## 

Ta thấy kết quả của chuẩn hóa min max đã cải thiện được chất lượng model khi tỷ lệ dự báo chính xác hợp đồng vỡ nợ cao hơn khi chưa chuẩn hóa (22.3% so với 21.8%).

Tiến hành benchmark model với các giá trị voting size khác nhau:

#Tao vong lap dieu chinh kich thuoc voting size
result <- data.frame()
df <- data.frame()
for (i in 1:25) {
  knn <- knn(train = select(training_chuanhoa, -BAD, -STT),test = select(testing_chuanhoa, -BAD, -STT), cl = cl_training_chuanhoa,k=i)
  df <- data.frame(voting_size = i, knn)
  result <- rbind(df,result)
}


#Tao vong lap luu label BAD cua testing ung voi moi vong lap
actual <- data.frame()
for (i in 1:25) {
  df <- data.frame(voting_size = i,BAD = testing_chuanhoa[,1])
  actual <- rbind(df,actual)
}

#Merge gia tri actual va gia tri forecast cua knn ung voi voting_size
result$actual <- actual$BAD

Hiển thị các kết quả tần xuất dự báo chính xác trên tập test

#Dự báo chính xác trường hợp nợ xấu
result %>% group_by(voting_size, actual, knn) %>%
  summarise(No_Loan = n()) %>% 
  filter(actual == 1, knn == 1) %>% 
  arrange(desc(No_Loan))
## Source: local data frame [25 x 4]
## Groups: voting_size, actual [25]
## 
## # A tibble: 25 x 4
##    voting_size actual knn   No_Loan
##          <int>  <int> <fct>   <int>
##  1           1      1 1         149
##  2           2      1 1         136
##  3           3      1 1         118
##  4           4      1 1         107
##  5           5      1 1         101
##  6           6      1 1          91
##  7           8      1 1          86
##  8           7      1 1          82
##  9           9      1 1          74
## 10          10      1 1          70
## # ... with 15 more rows
#Dự báo chính xác trường hợp không nợ xấu
result %>% group_by(voting_size, actual, knn) %>%
  summarise(No_Loan = n()) %>% 
  filter(actual == 0, knn == 0) %>% 
  arrange(desc(No_Loan))
## Source: local data frame [25 x 4]
## Groups: voting_size, actual [25]
## 
## # A tibble: 25 x 4
##    voting_size actual knn   No_Loan
##          <int>  <int> <fct>   <int>
##  1          15      0 0         949
##  2           9      0 0         948
##  3          10      0 0         948
##  4          11      0 0         948
##  5          12      0 0         948
##  6          13      0 0         948
##  7          14      0 0         948
##  8          21      0 0         948
##  9           4      0 0         947
## 10           5      0 0         947
## # ... with 15 more rows

Xây dựng model knn với voting size = 10

library(class)
set.seed(123)
#Lay factor nhan loai no cua tap traing
cl_training_chuanhoa <- as.factor(training_chuanhoa$BAD)

knn <- knn(train = select(training_chuanhoa, -BAD,-STT),test = select(testing_chuanhoa, -BAD,-STT), cl = cl_training,k=10)
# giá trị dự báo trên tập test:
head(knn,10)
##  [1] 1 1 1 0 1 1 1 0 1 0
## Levels: 0 1
#so sanh ket qua du bao tren tap test
CrossTable(knn,testing_chuanhoa$BAD)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## | Chi-square contribution |
## |           N / Row Total |
## |           N / Col Total |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  1193 
## 
##  
##              | testing_chuanhoa$BAD 
##          knn |         0 |         1 | Row Total | 
## -------------|-----------|-----------|-----------|
##            0 |       820 |       189 |      1009 | 
##              |     0.187 |     0.751 |           | 
##              |     0.813 |     0.187 |     0.846 | 
##              |     0.859 |     0.794 |           | 
##              |     0.687 |     0.158 |           | 
## -------------|-----------|-----------|-----------|
##            1 |       135 |        49 |       184 | 
##              |     1.026 |     4.117 |           | 
##              |     0.734 |     0.266 |     0.154 | 
##              |     0.141 |     0.206 |           | 
##              |     0.113 |     0.041 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |       955 |       238 |      1193 | 
##              |     0.801 |     0.199 |           | 
## -------------|-----------|-----------|-----------|
## 
## 

Kết quả chuẩn hóa theo standard deviation cho thấy tỷ lệ dự báo chính xác hợp đồng xấu đã tăng lên 22.7% so với trước chuẩn hóa là 21.8%.

Tiến hành benchmark model với các giá trị voting size khác nhau:

#Tao vong lap dieu chinh kich thuoc voting size
result <- data.frame()
df <- data.frame()
for (i in 1:25) {
  knn <- knn(train = select(training_chuanhoa, -BAD, -STT),test = select(testing_chuanhoa, -BAD, -STT), cl = cl_training_chuanhoa,k=i)
  df <- data.frame(voting_size = i, knn)
  result <- rbind(df,result)
}


#Tao vong lap luu label BAD cua testing ung voi moi vong lap
actual <- data.frame()
for (i in 1:25) {
  df <- data.frame(voting_size = i,BAD = testing_chuanhoa[,1])
  actual <- rbind(df,actual)
}

#Merge gia tri actual va gia tri forecast cua knn ung voi voting_size
result$actual <- actual$BAD

Hiển thị các kết quả tần xuất dự báo chính xác trên tập test

#Dự báo chính xác trường hợp nợ xấu
result %>% group_by(voting_size, actual, knn) %>%
  summarise(No_Loan = n()) %>% 
  filter(actual == 1, knn == 1) %>% 
  arrange(desc(No_Loan))
## Source: local data frame [25 x 4]
## Groups: voting_size, actual [25]
## 
## # A tibble: 25 x 4
##    voting_size actual knn   No_Loan
##          <int>  <int> <fct>   <int>
##  1           1      1 1         149
##  2           2      1 1         128
##  3           3      1 1         118
##  4           4      1 1         108
##  5           5      1 1         101
##  6           6      1 1          89
##  7           7      1 1          82
##  8           8      1 1          80
##  9           9      1 1          74
## 10          10      1 1          71
## # ... with 15 more rows
#Dự báo chính xác trường hợp không nợ xấu
result %>% group_by(voting_size, actual, knn) %>%
  summarise(No_Loan = n()) %>% 
  filter(actual == 0, knn == 0) %>% 
  arrange(desc(No_Loan))
## Source: local data frame [25 x 4]
## Groups: voting_size, actual [25]
## 
## # A tibble: 25 x 4
##    voting_size actual knn   No_Loan
##          <int>  <int> <fct>   <int>
##  1          15      0 0         949
##  2          16      0 0         949
##  3           6      0 0         948
##  4           9      0 0         948
##  5          10      0 0         948
##  6          11      0 0         948
##  7          12      0 0         948
##  8          13      0 0         948
##  9          14      0 0         948
## 10          21      0 0         948
## # ... with 15 more rows

Như vậy tỷ lệ dự báo chính xác của knn khi chuẩn hóa cũng tuân theo qui luật khi voting size tăng thì tỷ lệ dự báo chính xác hợp đồng vỡ nợ giảm và voting size giảm thì tỷ lệ dự báo chính xác hợp động không vỡ nợ tăng. Bài tiếp theo chúng ta sẽ làm một phép so sánh giữa phương pháp knn và các phương pháp khác trong hiệu quả xác định chính xác 1 hợp đồng vỡ nợ trên chính tập số liệu này.