Steps for Implementing a Machine Learning Project

Data Science in Banking Course

2018-03-13

#======================================================================================
#  Project Name: Breast Cancer (breast-cancer-wisconsin.data.txt)
#  Data Source + research papers: 
#  - https://archive.ics.uci.edu/ml/datasets/Breast+Cancer+Wisconsin+%28Diagnostic%29
#  - http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.56.707&rep=rep1&type=pdf
#======================================================================================


#---------------------------------
#  Initial Data Analysis (IDA)
#---------------------------------

# Đọc dữ liệu: 
rm(list = ls())
bc_data <- read.table("D:/Teaching/data_science_banking/breast_cancer/breast-cancer-wisconsin.data.txt", 
                      sep = ",")

# Đổi tên cho cột biến: 

colnames(bc_data) <- c("sample_code_number", 
                       "clump_thickness", 
                       "uniformity_of_cell_size", 
                       "uniformity_of_cell_shape", 
                       "marginal_adhesion", 
                       "single_epithelial_cell_size", 
                       "bare_nuclei", 
                       "bland_chromatin", 
                       "normal_nucleoli", 
                       "mitosis", 
                       "classes")

library(tidyverse)
library(magrittr)

# Xem qua dữ liệu: 
# bc_data %>% head()
# bc_data %>% str()

# Viết hàm đánh giá tỉ lệ thiếu của dữ liệu: 

ti_le_na <- function(x) {100*sum(is.na(x) / length(x))}

# Cách 1: 
bc_data %>% summarise_all(ti_le_na)
##   sample_code_number clump_thickness uniformity_of_cell_size
## 1                  0               0                       0
##   uniformity_of_cell_shape marginal_adhesion single_epithelial_cell_size
## 1                        0                 0                           0
##   bare_nuclei bland_chromatin normal_nucleoli mitosis classes
## 1           0               0               0       0       0
# Cách 2: 
sapply(bc_data, ti_le_na)
##          sample_code_number             clump_thickness 
##                           0                           0 
##     uniformity_of_cell_size    uniformity_of_cell_shape 
##                           0                           0 
##           marginal_adhesion single_epithelial_cell_size 
##                           0                           0 
##                 bare_nuclei             bland_chromatin 
##                           0                           0 
##             normal_nucleoli                     mitosis 
##                           0                           0 
##                     classes 
##                           0
#-----------------------
#  Pre-processing Data
#-----------------------

# Dán lại nhãn cho biến classes: 

dan_nhan <- function(x) {
  case_when(x == 2 ~ "B", 
            x == 4 ~ "M")
}


# Thay thế "?" thành nhãn có tần suất cao nhất: 
bc_data$bare_nuclei %>% table()
## .
##   ?   1  10   2   3   4   5   6   7   8   9 
##  16 402 132  30  28  19  30   4   8  21   9
replace_na <- function(x) {
  ELSE <- TRUE
  case_when(x == "?" ~ "1", 
            ELSE ~ x)
}


# Sử dụng các hàm trên: 

bc_data %<>% mutate(classes = dan_nhan(classes), 
                    bare_nuclei = as.character(bare_nuclei), 
                    bare_nuclei = replace_na(bare_nuclei))

# bc_data %>% head()
bc_data$bare_nuclei %>% table()
## .
##   1  10   2   3   4   5   6   7   8   9 
## 418 132  30  28  19  30   4   8  21   9
# Chuyển hóa bare_nuclei về đúng bản chất số: 
bc_data %<>% mutate(bare_nuclei = as.numeric(bare_nuclei))
# bc_data %>% head()

# Loại bỏ biến không cần thiết: 
bc_data %<>% select(-sample_code_number)

#------------------------------------
#  Exploratory Data Analysis (EDA)
#------------------------------------

# Tỉ lệ của các nhãn: 

bc_data$classes %>% table() / nrow(bc_data)
## .
##         B         M 
## 0.6552217 0.3447783
# Hình ảnh hóa tỉ lệ này: 

theme_set(theme_minimal())
bc_data %>% 
  group_by(classes) %>% 
  count() %>% 
  ggplot(aes(classes, n)) + 
  geom_col()

# Cách 1: 
bc_data %>% 
  gather(features, value, -classes) %>% 
  ggplot(aes(classes, value)) + 
  geom_boxplot() + 
  facet_wrap(~ features, scales = "free")

# Cách 2: 

bc_data %>% 
  ggplot(aes(bare_nuclei, fill = classes, color = classes)) + 
  geom_density(alpha = 0.3)

# Cách 3: 

bc_data %>% 
  gather(features, value, -classes) %>% 
  ggplot(aes(value, fill = classes, color = classes)) + 
  geom_density(alpha = 0.3) + 
  facet_wrap(~ features, scales = "free")

# Tương quan giữa các biến là không cao: 
library(corrplot)
bc_data %>% 
  select(-classes) %>% 
  cor() %>% 
  corrplot(method = "color", order = "hclust")

#---------------------------------
#     Support Vector Machine
#---------------------------------

# Để sử dụng SVM cho phân loại cần chuyển hóa biến đích về factor: 
bc_data %<>% mutate_if(is.character, as.factor)


# Chuẩn bị dữ liệu: 

set.seed(1)
train <- bc_data %>% 
  group_by(classes) %>% 
  sample_frac(0.8) %>% 
  ungroup()

test <- setdiff(bc_data, train)

# Chạy SVM với cost =  10  và gamma = 0.1:
library(e1071)
set.seed(29)
svm_radial <- svm(classes ~., 
                  data = train,
                  method = "C-classification", 
                  kernel = "radial",
                  cost = 10, 
                  gamma = 0.1)

# Sơ bộ mô hình: 
summary(svm_radial)
## 
## Call:
## svm(formula = classes ~ ., data = train, method = "C-classification", 
##     kernel = "radial", cost = 10, gamma = 0.1)
## 
## 
## Parameters:
##    SVM-Type:  C-classification 
##  SVM-Kernel:  radial 
##        cost:  10 
##       gamma:  0.1 
## 
## Number of Support Vectors:  62
## 
##  ( 22 40 )
## 
## 
## Number of Classes:  2 
## 
## Levels: 
##  B M
#-----------------------------------
#  Evaluating Model Performance
#-----------------------------------

# Sử dụng mô hình cho dự báo: 
pred1 <- predict(svm_radial, test, decision.values = TRUE)

# Đánh giá mô hình (cách 1): 
mean(pred1 == test$classes)
## [1] 0.9135802
# Đánh giá mô hình (cách 2): 
library(caret) 
confusionMatrix(pred1, test$classes, positive = "M")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  B  M
##          B 29  1
##          M  6 45
##                                         
##                Accuracy : 0.9136        
##                  95% CI : (0.83, 0.9645)
##     No Information Rate : 0.5679        
##     P-Value [Acc > NIR] : 7.279e-12     
##                                         
##                   Kappa : 0.8209        
##  Mcnemar's Test P-Value : 0.1306        
##                                         
##             Sensitivity : 0.9783        
##             Specificity : 0.8286        
##          Pos Pred Value : 0.8824        
##          Neg Pred Value : 0.9667        
##              Prevalence : 0.5679        
##          Detection Rate : 0.5556        
##    Detection Prevalence : 0.6296        
##       Balanced Accuracy : 0.9034        
##                                         
##        'Positive' Class : M             
## 
# SVM với một cặp tham số khác: 
set.seed(123)
tune_svm <- svm(classes ~., 
                data = train,
                method = "C-classification", 
                kernel = "radial",
                cost = 10, 
                gamma = 0.01)


# Dự báo trên mô hình tốt nhất này: 
best_pred <- predict(tune_svm, test, decision.values = TRUE)
confusionMatrix(best_pred, test$classes, positive = "M")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  B  M
##          B 30  1
##          M  5 45
##                                           
##                Accuracy : 0.9259          
##                  95% CI : (0.8457, 0.9723)
##     No Information Rate : 0.5679          
##     P-Value [Acc > NIR] : 8.746e-13       
##                                           
##                   Kappa : 0.847           
##  Mcnemar's Test P-Value : 0.2207          
##                                           
##             Sensitivity : 0.9783          
##             Specificity : 0.8571          
##          Pos Pred Value : 0.9000          
##          Neg Pred Value : 0.9677          
##              Prevalence : 0.5679          
##          Detection Rate : 0.5556          
##    Detection Prevalence : 0.6173          
##       Balanced Accuracy : 0.9177          
##                                           
##        'Positive' Class : M               
## 
#-------------------------------------------
#    Comparing with some alternatives
#-------------------------------------------

# Đánh giá mô hình: 

acc_model1 <- c()
acc_model2 <- c()

for (i in 1:100) {
  set.seed(i)
  test100 <- bc_data %>% 
    group_by(classes) %>% 
    sample_frac(0.3) %>% 
    ungroup()
  
  pred1 <- predict(svm_radial, test100, decision.values = TRUE)
  acc1 <- mean(pred1 == test100$classes)
  acc_model1 <- c(acc_model1, acc1)
  
  pred2 <- predict(tune_svm, test100, decision.values = TRUE)
  acc2 <- mean(pred2 == test100$classes)
  acc_model2 <- c(acc_model2, acc2)
}

# Các kết quả ở dạng data frame: 
all_df <- data.frame(Accuracy = c(acc_model1, acc_model2), 
                     Model = c(rep("Model1", 100), rep("Model2", 100)))

# Đánh giá sơ bộ hiệu quả phân loại của hai mô hình bằng hình ảnh: 
all_df %>% 
  ggplot(aes(Model, Accuracy)) + 
  geom_boxplot()

all_df %>% 
  ggplot(aes(Accuracy)) + geom_density(alpha = 0.3, color = "blue", fill = "blue") + 
  geom_histogram(aes(y = ..density..), alpha = 0.3, color = "red", fill = "red") + 
  facet_wrap(~ Model)

t.test(all_df$Accuracy ~ all_df$Model)
## 
##  Welch Two Sample t-test
## 
## data:  all_df$Accuracy by all_df$Model
## t = 9.4719, df = 198, p-value < 2.2e-16
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
##  0.008524204 0.013006897
## sample estimates:
## mean in group Model1 mean in group Model2 
##            0.9804306            0.9696651
#---------------------------------
#  Improving Model Performance
#---------------------------------

# Viết hàm chuẩn hóa dữ liệu: 

scale_01 <- function(x) {
  y <- (x - min(x)) / (max(x) - min(x))
  return(y)
}

# Chuẩn hóa dữ liệu: 
bc_data_scaled <- bc_data %>% mutate_if(is.numeric, scale_01)


set.seed(1995)
train <- bc_data_scaled %>% 
  group_by(classes) %>% 
  sample_frac(0.8) %>% 
  ungroup()

test <- setdiff(bc_data_scaled, train)

# Chạy SVM với cost =  10  và gamma = 0.1:

set.seed(29)
svm_radial_scaled <- svm(classes ~.,
                         data = train,
                         method = "C-classification", 
                         kernel = "radial",
                         cost = 10, 
                         gamma = 0.1)


pred1 <- predict(svm_radial_scaled, test, decision.values = TRUE)
mean(pred1 == test$classes)
## [1] 0.9518072
acc_model3 <- c()

for (i in 1:100) {
  set.seed(i)
  test100 <- bc_data_scaled %>% 
    group_by(classes) %>% 
    sample_frac(0.3) %>% 
    ungroup()
  
  pred3 <- predict(svm_radial_scaled, test100, decision.values = TRUE)
  acc3 <- mean(pred3 == test100$classes)
  acc_model3 <- c(acc_model3, acc3)
  
}

all_df3 <- data.frame(Accuracy = c(acc_model1, acc_model2, acc_model3), 
                      Model = c(rep("Model1", 100), 
                                rep("Model2", 100), 
                                rep("Model3", 100)))
all_df3 %>% 
  ggplot(aes(Model, Accuracy)) +
  geom_boxplot()

all_df3 %>% 
  group_by(Model) %>% 
  summarise_each(funs(mean, median, min, max, sd), Accuracy) %>% 
  arrange(-Accuracy_mean)
## # A tibble: 3 x 6
##   Model  Accuracy_mean Accuracy_median Accuracy_min Accuracy_max
##   <fct>          <dbl>           <dbl>        <dbl>        <dbl>
## 1 Model3         0.988           0.990        0.976        1.00 
## 2 Model1         0.980           0.981        0.952        1.00 
## 3 Model2         0.970           0.971        0.943        0.986
## # ... with 1 more variable: Accuracy_sd <dbl>