A Big Project for Data Science in Banking Course (Part 2)

Nguyen Chi Dung

#======================================
#     Big Project Part 2: hmeq.csv
#======================================

#--------------------------------------
#     IDA / Preprocessing Data 
#-------------------------------------

# Đọc dữ liệu: 
rm(list = ls())
library(tidyverse)
library(magrittr)

hmeq <- read.csv("D:/Teaching/data_science_banking/hmeq/hmeq.csv")

# Viết hàm điều tra tỉ lệ dữ liệu thiếu ở từng cột biến: 
ti_le_na <- function(x) {100*sum(is.na(x)) / length(x)}

# Kiểm tra NA: 
hmeq %>% summarise_all(funs(ti_le_na))
##   BAD LOAN  MORTDUE    VALUE REASON JOB     YOJ    DEROG   DELINQ    CLAGE
## 1   0    0 8.691275 1.879195      0   0 8.64094 11.87919 9.731544 5.167785
##       NINQ     CLNO  DEBTINC
## 1 8.557047 3.724832 21.25839
# Viết hàm thay thế  NA bằng mean cho biến liên tục: 

thay_na_mean <- function(x) {
  tb <- mean(x, na.rm = TRUE)
  x[is.na(x)] <- tb
  return(x)
}

# Viết hàm thay thế NA bằng lớp xuất hiện nhiều nhất
# cho biến định tính (đề phòng nếu cần dùng đến): 

thay_na_factor <- function(x) {
  u <- x %>% 
    table(useNA = "ifany") %>% 
    as.data.frame() %>% 
    arrange(-Freq)
  k <- as.character(u[, 1])
  x[is.na(x)] <- k[1]
  return(x)
}

# Test hàm: 

x <- c("A", "A", "B", "C", "A", NA)
thay_na_factor(x)
## [1] "A" "A" "B" "C" "A" "A"
# Nghề nghiệp và lí do vay có một số không được đặt tên: 
table(hmeq$REASON)
## 
##         DebtCon HomeImp 
##     252    3928    1780
table(hmeq$JOB)
## 
##             Mgr  Office   Other ProfExe   Sales    Self 
##     279     767     948    2388    1276     109     193
# Nên ta viết hàm đặt tên lại cho lí do vay: 
name_job <- function(x) {
  x %<>% as.character()
  ELSE <- TRUE
  quan_tam <- c("Mgr", "Office", "Other", "ProfExe", "Sales", "Self")
  case_when(!x %in% quan_tam ~ "Other", 
            ELSE ~ x)
}

# Tương tự là cho lí do vay: 
name_reason <- function(x) {
  ELSE <- TRUE
  x %<>% as.character()
  case_when(!x %in% c("DebtCon", "HomeImp") ~ "Unknown", 
            ELSE ~ x)
}


# Xử lí số liệu thiếu và dán nhãn lại: 
hmeq_proce <- hmeq %>% 
  mutate_if(is.numeric, thay_na_mean) %>% 
  mutate_at("REASON", name_reason) %>% 
  mutate_at("JOB", name_job)

# Kiểm tra lại rằng không còn NA: 
hmeq_proce %>% summarise_all(funs(ti_le_na))
##   BAD LOAN MORTDUE VALUE REASON JOB YOJ DEROG DELINQ CLAGE NINQ CLNO
## 1   0    0       0     0      0   0   0     0      0     0    0    0
##   DEBTINC
## 1       0
#Dán lại nhãn trong đó 1 = Bad, 0 = Good:
hmeq_proce %<>% mutate(BAD = case_when(BAD == 1 ~ "Bad",
                                       BAD == 0 ~ "Good"))

# Chuyển hóa bất kì cột biến nào ở character về factor: 
hmeq_proce %<>% mutate_if(is.character, as.factor)


# Tỉ lệ các loại hồ sơ: 
hmeq_proce %>% 
  group_by(BAD) %>% 
  count() %>% 
  mutate(Percent = 100*n / nrow(hmeq_proce)) %>% 
  knitr::kable()
BAD n Percent
Bad 1189 19.94966
Good 4771 80.05034
# Thực  hiện  phân chia dữ liệu theo tỉ lệ 50 -  50: 
library(caret)
set.seed(29)
id <- createDataPartition(y = hmeq_proce$BAD, p = 0.5, list = FALSE)

train_data <- hmeq_proce[id, ]
test_data <- hmeq_proce[-id, ]


#--------------------------------------------------
#   Thực hiện và so sánh nhanh một số thuật toán
#--------------------------------------------------

# Thiết lập các chế độ kiểm tra cho mô hình bằng cross - validation: 

set.seed(1)
ctrl <- trainControl(method = "repeatedcv", 
                     # k = 5: 
                     number = 5, 
                     # Lặp lại 6 lần: 
                     repeats = 10,
                     # Lấy ra tất cả 12 tiêu chí đánh giá mô hình: 
                     summaryFunction = multiClassSummary, 
                     # Sử dụng tính toán song song: 
                     allowParallel = TRUE)


# Thiết lập các chế độ tính toán song song: 

library(doParallel)
n_cores <- detectCores()
registerDoParallel(cores = n_cores - 1)

# Logistic (chú ý rằng Sensitivity = BB / (BB + GB))

logistic <- train(BAD ~., 
                  data = train_data, 
                  method = "glm",
                  family = "binomial", 
                  trControl = ctrl, 
                  metric = "Sensitivity", 
                  preProcess = NULL)

# Probit: 

probit <- train(BAD ~., 
                data = train_data, 
                method = "glm",
                family = "binomial"(link = "probit"), 
                trControl = ctrl, 
                metric = "Sensitivity", 
                preProcess = NULL)


# Support Vector Machine (SVM): 

set.seed(1)
svm <- train(BAD ~., 
             data = train_data, 
             method = "svmRadial",
             trControl = ctrl, 
             metric = "Sensitivity", 
             preProcess = c("scale"))



# Random Forest: 

set.seed(1)
rf <- train(BAD ~., 
            data = train_data, 
            method = "rf",
            trControl = ctrl, 
            metric = "Sensitivity", 
            preProcess = c("scale"))


# KNN: 
set.seed(1)
knn <- train(BAD ~., 
            data = train_data, 
            method = "knn",
            trControl = ctrl, 
            metric = "Sensitivity", 
            preProcess = c("scale"))


# Data Frame về kết quả của các mô hình này: 
result_df <- bind_rows(logistic$resample %>% 
                         select(-Resample) %>% 
                         mutate(Model = "Logistic"), 
                       probit$resample %>% 
                         select(-Resample) %>% 
                         mutate(Model = "Probit"), 
                       svm$resample %>% 
                         select(-Resample) %>% 
                         mutate(Model = "SVM"), 
                       rf$resample %>% 
                         select(-Resample) %>% 
                         mutate(Model = "RF"), 
                       knn$resample %>% 
                         select(-Resample) %>% 
                         mutate(Model = "knn"))


# RF là mô hình có ưu thế nhất: 
theme_set(theme_minimal())
result_df %>% 
  gather(metric, value, -Model) %>% 
  ggplot(aes(Model, value)) + 
  geom_boxplot(aes(fill = Model, color = Model), alpha = 0.3, show.legend = FALSE) + 
  facet_wrap(~ metric, scales = "free")

result_df %>% 
  group_by(Model) %>% 
  summarise_each(funs(mean, median, sd, min, max, n()), Accuracy) %>% 
  arrange(-Accuracy_mean)
## # A tibble: 5 x 7
##   Model    Accuracy_mean Accuracy_median Accuracy_sd Accuracy_min
##   <chr>            <dbl>           <dbl>       <dbl>        <dbl>
## 1 RF               0.897           0.898     0.0101         0.874
## 2 SVM              0.856           0.856     0.00723        0.841
## 3 knn              0.855           0.855     0.00967        0.834
## 4 Logistic         0.831           0.831     0.00796        0.815
## 5 Probit           0.829           0.831     0.0109         0.804
## # ... with 2 more variables: Accuracy_max <dbl>, Accuracy_n <int>
result_df %>% 
  group_by(Model) %>% 
  summarise_each(funs(mean, median, sd, min, max, n()), Sensitivity) %>% 
  arrange(-Sensitivity_mean)
## # A tibble: 5 x 7
##   Model   Sensitivity_mean Sensitivity_med~ Sensitivity_sd Sensitivity_min
##   <chr>              <dbl>            <dbl>          <dbl>           <dbl>
## 1 RF                 0.685            0.685         0.0406           0.597
## 2 knn                0.334            0.328         0.0451           0.252
## 3 SVM                0.306            0.303         0.0337           0.235
## 4 Logist~            0.277            0.277         0.0314           0.193
## 5 Probit             0.264            0.261         0.0400           0.151
## # ... with 2 more variables: Sensitivity_max <dbl>, Sensitivity_n <int>
result_df %>% 
  group_by(Model) %>% 
  summarise_each(funs(mean, median, sd, min, max, n()), Specificity) %>% 
  arrange(-Specificity_mean)
## # A tibble: 5 x 7
##   Model   Specificity_mean Specificity_med~ Specificity_sd Specificity_min
##   <chr>              <dbl>            <dbl>          <dbl>           <dbl>
## 1 SVM                0.993            0.994        0.00459           0.979
## 2 knn                0.985            0.985        0.00601           0.971
## 3 Probit             0.970            0.970        0.00887           0.950
## 4 Logist~            0.969            0.969        0.00781           0.950
## 5 RF                 0.950            0.951        0.00982           0.925
## # ... with 2 more variables: Specificity_max <dbl>, Specificity_n <int>
result_df %>% 
  group_by(Model) %>% 
  summarise_each(funs(mean, median, sd, min, max, n()), Kappa) %>% 
  arrange(-Kappa_mean)
## # A tibble: 5 x 7
##   Model    Kappa_mean Kappa_median Kappa_sd Kappa_min Kappa_max Kappa_n
##   <chr>         <dbl>        <dbl>    <dbl>     <dbl>     <dbl>   <int>
## 1 RF            0.663        0.665   0.0341     0.589     0.718      50
## 2 knn           0.412        0.411   0.0490     0.319     0.512      50
## 3 SVM           0.399        0.396   0.0380     0.324     0.492      50
## 4 Logistic      0.316        0.317   0.0361     0.233     0.400      50
## 5 Probit        0.303        0.306   0.0500     0.175     0.402      50
result_df %>% 
  ggplot(aes(Model, Accuracy)) + 
  geom_boxplot()

#--------------------------------------------------
#    Mô phỏng profit của việc sử dụng mô hình
#--------------------------------------------------

# Viết hàm: 

test_model <- function(model, so_lan_lay_mau, so_ho_so, du_lieu) {
  ket_qua <- data.frame()
  for (i in 1:so_lan_lay_mau) {
    set.seed(i)
    id <- createDataPartition(y = du_lieu$BAD, 
                              p = so_ho_so / nrow(du_lieu), 
                              list = FALSE)
    test <- du_lieu[id, ]
    
    predicted <- predict(model, test, decision.values = TRUE)
    u <- table(predicted, test$BAD) %>% as.vector()
    ket_qua <- rbind(ket_qua, u)
    names(ket_qua) <- c("BB", "GB", "BG", "GG")
  }
  return(ket_qua)
}

# Sử dụng hàm: 
df_rf <- test_model(rf, 100, 1000, hmeq_proce)
df_knn <- test_model(knn, 100, 1000, hmeq_proce)


n_vay_tot <- df_rf$GG %>% sum()
n_vay_xau <- df_rf$BG %>% sum()

khoan_vay <- hmeq_proce$LOAN


so_tien_cho_vay_tot <- sample(khoan_vay, n_vay_tot, replace = TRUE)
so_tien_cho_vay_xau <- sample(khoan_vay, n_vay_xau, replace = TRUE)

loi_nhuan <- sum(0.1*so_tien_cho_vay_tot) - sum(so_tien_cho_vay_xau)
loi_nhuan
## [1] 108619520
# Viết hàm mô phỏng lợi nhuận: 
profit_simu <- function(data_from_model, rate, so_lan_mo_phong) {
  n_vay_tot <- data_from_model$GG %>% sum()
  n_vay_xau <- data_from_model$BG %>% sum()
  prof <- c()
  
  for (i in 1:so_lan_mo_phong) {
    so_tien_cho_vay_tot <- sample(khoan_vay, n_vay_tot, replace = TRUE)
    so_tien_cho_vay_xau <- sample(khoan_vay, n_vay_xau, replace = TRUE)
    
    loi_nhuan <- sum(rate*so_tien_cho_vay_tot) - sum(so_tien_cho_vay_xau)
    prof <- c(prof, loi_nhuan)
  }
  return(prof)
  
}

# Sử dụng hàm: 
profit_simu(data_from_model = df_rf, 
            rate = 0.1, 
            so_lan_mo_phong = 5000) -> p1


profit_simu(data_from_model = df_knn, 
            rate = 0.1, 
            so_lan_mo_phong = 5000) -> p2


# Tạo ra DF về lợi nhuận: 

df_for_comp <- bind_rows(data_frame(Profit = p1, Model = rep("RF", length(p1))), 
                         data_frame(Profit = p2, Model = rep("KNN", length(p2))))

df_for_comp %>% 
  group_by(Model) %>% 
  summarise_each(funs(mean, median, min, max, sd), Profit)
## # A tibble: 2 x 6
##   Model Profit_mean Profit_median Profit_min Profit_max Profit_sd
##   <chr>       <dbl>         <dbl>      <dbl>      <dbl>     <dbl>
## 1 KNN    139851678.    139850035. 138584640. 141277700.   394391.
## 2 RF     108781475.    108778530. 106422590. 110962040.   587925.
df_for_comp %>% 
  ggplot(aes(Profit / 1000000)) + 
  geom_density(fill = "red", color = "red", alpha = 0.3) + 
  geom_histogram(aes(y = ..density..), color = "blue", fill = "blue", alpha = 0.3) + 
  facet_wrap(~ Model, scales = "free")