Pengenalan

Dataset terkait dengan kampanye pemasaran langsung dari lembaga perbankan Portugis. Kampanye pemasaran didasarkan pada panggilan telepon. Seringkali, lebih dari satu kontak ke klien yang sama dibutuhkan, untuk mengakses apakah produk (deposito bank) akan iya (‘yes’) atau tidak (‘no’) berlangganan. Kemudian digunakan untuk membuat model Naive Bayes, Decision Tree dan Random Forest untuk memprediksi adanya kemungkinan customer berlangganan deposito bank. Kita akan menggunakan dataset Bank bisa dilihat disini

Apa yang akan kita lakukan ?

Kita akan mengolah data dengan Naive Bayes, Decision Tree dan Random Forest menggunakan dataset Bank. Langkah - langkah yang akan kita lakukan sebagai berikut:

  • Kita akan menetapkan y sebagai variabel target dan variabel lainnya sebagai prediktor
  • Membuat model Naive Bayes, Decision Tree dan Random Forest untuk menjelaskan variabel y berdasarkan prediktor
  • Menguji validitas model
  • Membuat evaluasi dari ketiga model
  • Memberi kesimpulan dari ketiga model

Persiapan Data

Import Library

library(dplyr) 
library(DT) 
library(caret)
library(e1071)
library(partykit)
library(ROCR)
library(explore)

Import Data

data <- read.csv2("bank.csv")
glimpse(data)
## Rows: 4,521
## Columns: 17
## $ age       <int> 30, 33, 35, 30, 59, 35, 36, 39, 41, 43, 39, 43, 36, 20, 3...
## $ job       <chr> "unemployed", "services", "management", "management", "bl...
## $ marital   <chr> "married", "married", "single", "married", "married", "si...
## $ education <chr> "primary", "secondary", "tertiary", "tertiary", "secondar...
## $ default   <chr> "no", "no", "no", "no", "no", "no", "no", "no", "no", "no...
## $ balance   <int> 1787, 4789, 1350, 1476, 0, 747, 307, 147, 221, -88, 9374,...
## $ housing   <chr> "no", "yes", "yes", "yes", "yes", "no", "yes", "yes", "ye...
## $ loan      <chr> "no", "yes", "no", "yes", "no", "no", "no", "no", "no", "...
## $ contact   <chr> "cellular", "cellular", "cellular", "unknown", "unknown",...
## $ day       <int> 19, 11, 16, 3, 5, 23, 14, 6, 14, 17, 20, 17, 13, 30, 29, ...
## $ month     <chr> "oct", "may", "apr", "jun", "may", "feb", "may", "may", "...
## $ duration  <int> 79, 220, 185, 199, 226, 141, 341, 151, 57, 313, 273, 113,...
## $ campaign  <int> 1, 1, 1, 4, 1, 2, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 5, 1, 1, ...
## $ pdays     <int> -1, 339, 330, -1, -1, 176, 330, -1, -1, 147, -1, -1, -1, ...
## $ previous  <int> 0, 4, 1, 0, 0, 3, 2, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 2, 0, ...
## $ poutcome  <chr> "unknown", "failure", "failure", "unknown", "unknown", "f...
## $ y         <chr> "no", "no", "no", "no", "no", "no", "no", "no", "no", "no...

Dataset bank memiliki variabel-variabel sebagai berikut:

  • age
  • job : type of job
  • marital : marital status
  • education
  • default: has credit in default?
  • balance: average yearly balance, in euros
  • housing: has housing loan?
  • loan: has personal loan?
  • contact: contact communication type
  • day: last contact day of the month
  • month: last contact month of year
  • duration: last contact duration, in seconds
  • campaign: number of contacts performed during this campaign and for this client
  • pdays: number of days that passed by after the client was last contacted from a previous campaign
  • previous: number of contacts performed before this campaign and for this client
  • poutcome: outcome of the previous marketing campaign
  • y : has the client subscribed a term deposit?

Dataset memiliki 4521 baris dan 17 kolom. Variabel target kita adalah y dan kita akan menggunakan variabel lain sebagai prediktor.

Data Wrangling

Langkah pertama kita cek apakah terdapat missing value pada dateset

colSums(is.na(data))
##       age       job   marital education   default   balance   housing      loan 
##         0         0         0         0         0         0         0         0 
##   contact       day     month  duration  campaign     pdays  previous  poutcome 
##         0         0         0         0         0         0         0         0 
##         y 
##         0

Dataset tidak terdapat missing value

Mari kita ubah beberapa tipe data variabel yang masih belum tepat

data <- data %>% 
  mutate(job = as.factor(job),
         marital = as.factor(marital),
         education = as.factor(education),
         default = as.factor(default),
         housing = as.factor(housing),
         loan = as.factor(loan),
         contact = as.factor(contact),
         month = as.factor(month),
         poutcome = as.factor(poutcome),
         y = as.factor(y))

Memahami Data

data %>% explore(age)

data %>% describe(age)
## variable = age
## type     = integer
## na       = 0 of 4 521 (0%)
## unique   = 67
## min|max  = 19 | 87
## q05|q95  = 27 | 59
## q25|q75  = 33 | 49
## median   = 39
## mean     = 41.1701

Jadi, untuk customer dengan usia 19-87 tahun mediannya adalah 39 tahun dan puncaknya pada usia 33-49

data %>% explore(marital)

61.9% dari customer sudah menikah, 26.5 masih single dan sisanya pernah bercerai.

data %>% explore(age, marital)

Berikut gambaran persebaran usia customer dengan marital status. Single memiliki usia terendah.

data %>% explore(job)

Grafik di atas merupakan persentasi banyaknya customer di setiap bidang kerja

data %>% explore(age, job)

Sementara itu, ini persebaran umur customer disetiap bidang kerja

data %>% explore(education)

Grafik di atas menggambarkan persenan customer di setiap bidang education

data %>% 
  explore_all(target = y, split = FALSE)

data %>% explore(y)

Dari dataset terdapat sebanyak 88.5% customer yang menyatakan No atau tidak ingin berlangganan produk deposito bank

Modelling

Pisahkan data

Sebelumnya kita perlu membagi data menjadi dataset train dan dataset uji. Kita akan menggunakan dataset train untuk melatih model Naive Bayes, Decision Tree, dan Random Forest. Dataset pengujian akan digunakan sebagai pembanding dan melihat apakah model menjadi overfit atau tidak dalam memprediksi data baru yang belum terlihat selama fase pelatihan.

Kita akan membagi sebanyak 80% dari data sebagai data pelatihan dan 20% sebagai data pengujian.

RNGkind(sample.kind = "Rounding")
set.seed(100)

intrain <- sample(nrow(data), nrow(data)*0.8)
data_train <- data[intrain,]
data_test <- data[-intrain,]

Kita cek kembali proporsi target pada data train

prop.table(table(data_train$y))
## 
##        no       yes 
## 0.8821903 0.1178097

Proporsi kelas target ternyata tidak seimbang. oleh karena itu kita akan melakukan DownSample untuk membuat proporsi kelas target seimbang

data_train <- downSample(x = data_train %>% select(-y), 
                         y = data_train$y, 
                         yname = "y") 
prop.table(table(data_train$y))
## 
##  no yes 
## 0.5 0.5

Sekarang proporsi kelas target kita sudah seimbang, kita bisa lanjut ke tahap selanjutnya

Naive Bayes

Model

model_naive <- naiveBayes(x = data_train %>% select(-y),
                          y = data_train$y,
                          laplace = 1)

Prediction

Lakukan prediksi kelas ke data_test dan simpan ke objek naive_class

naive_class <- predict(model_naive, data_test, type = "class")
head(naive_class)
## [1] yes no  no  no  yes no 
## Levels: no yes

Evaluation

Buat Confusion Matrix dan evaluasi performa model

confusionMatrix(naive_class, data_test$y, positive = "yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  no yes
##        no  621  27
##        yes 189  68
##                                           
##                Accuracy : 0.7613          
##                  95% CI : (0.7322, 0.7888)
##     No Information Rate : 0.895           
##     P-Value [Acc > NIR] : 1               
##                                           
##                   Kappa : 0.2753          
##                                           
##  Mcnemar's Test P-Value : <2e-16          
##                                           
##             Sensitivity : 0.71579         
##             Specificity : 0.76667         
##          Pos Pred Value : 0.26459         
##          Neg Pred Value : 0.95833         
##              Prevalence : 0.10497         
##          Detection Rate : 0.07514         
##    Detection Prevalence : 0.28398         
##       Balanced Accuracy : 0.74123         
##                                           
##        'Positive' Class : yes             
## 

Decision Tree

Model

model_dtree <- ctree(formula = y ~ ., data = data_train)

plot(model_dtree, type = "simple")

Prediction

Lakukan prediksi kelas ke data_test dan simpan ke objek dtree_class

dtree_class <- predict(model_dtree, data_test, type = "response")
head(dtree_class)
##  1  5  8  9 13 21 
## no no no no no no 
## Levels: no yes

Evaluation

confusionMatrix(dtree_class, data_test$y, positive = "yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  no yes
##        no  636  24
##        yes 174  71
##                                           
##                Accuracy : 0.7812          
##                  95% CI : (0.7528, 0.8078)
##     No Information Rate : 0.895           
##     P-Value [Acc > NIR] : 1               
##                                           
##                   Kappa : 0.3138          
##                                           
##  Mcnemar's Test P-Value : <2e-16          
##                                           
##             Sensitivity : 0.74737         
##             Specificity : 0.78519         
##          Pos Pred Value : 0.28980         
##          Neg Pred Value : 0.96364         
##              Prevalence : 0.10497         
##          Detection Rate : 0.07845         
##    Detection Prevalence : 0.27072         
##       Balanced Accuracy : 0.76628         
##                                           
##        'Positive' Class : yes             
## 

Dari hasil yang didapatkan, performa model tidak cukup baik. Mari cek apakah hal tersebut karena model mengalami overfitting

dtree_class_train <- predict(object = model_dtree, newdata = data_train %>%
                               select(-y),
                             type = "response")


confusionMatrix(data = dtree_class_train, reference = data_train$y,
                positive = "yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  no yes
##        no  346  92
##        yes  80 334
##                                           
##                Accuracy : 0.7981          
##                  95% CI : (0.7696, 0.8246)
##     No Information Rate : 0.5             
##     P-Value [Acc > NIR] : <2e-16          
##                                           
##                   Kappa : 0.5962          
##                                           
##  Mcnemar's Test P-Value : 0.4016          
##                                           
##             Sensitivity : 0.7840          
##             Specificity : 0.8122          
##          Pos Pred Value : 0.8068          
##          Neg Pred Value : 0.7900          
##              Prevalence : 0.5000          
##          Detection Rate : 0.3920          
##    Detection Prevalence : 0.4859          
##       Balanced Accuracy : 0.7981          
##                                           
##        'Positive' Class : yes             
## 

Mari coba gunakan random forest untuk mencegah kondisi overfitting namun tetap menghasilkan performa yang baik

Random Forest

Model

RNGkind(sample.kind = "Rounding")
set.seed(100)

ctrl <- trainControl(method="repeatedcv", number = 5, repeats = 3)

model_forest <- train(y ~ ., data = data_train, method = "rf", trControl = ctrl)

saveRDS(model_forest, "model_forest.RDS") # simpan model
model_forest <- readRDS("model_forest.RDS")

Mari lihat summary dari model random forest:

model_forest
## Random Forest 
## 
## 852 samples
##  16 predictor
##   2 classes: 'no', 'yes' 
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold, repeated 3 times) 
## Summary of sample sizes: 682, 682, 682, 682, 680, 681, ... 
## Resampling results across tuning parameters:
## 
##   mtry  Accuracy   Kappa    
##    2    0.7589991  0.5180499
##   22    0.8133623  0.6267631
##   42    0.8023957  0.6048299
## 
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was mtry = 22.

Mari lihat Out of Bag Error yang dihasilkan dari model random forest

library(randomForest)
model_forest$finalModel
## 
## Call:
##  randomForest(x = x, y = y, mtry = param$mtry) 
##                Type of random forest: classification
##                      Number of trees: 500
## No. of variables tried at each split: 22
## 
##         OOB estimate of  error rate: 19.13%
## Confusion matrix:
##      no yes class.error
## no  325 101   0.2370892
## yes  62 364   0.1455399

OOB yang dihasilkan mengindikasikan bahwa model akan memiliki akurasi sebesar 80.87% di data baru.

Kita juga bisa melihat variable apa saja yang penting dalam pembuatan random forest dengan menggunakan varImp().

varImp(model_forest)
## rf variable importance
## 
##   only 20 most important variables shown (out of 42)
## 
##                   Overall
## duration          100.000
## balance            23.155
## age                20.583
## day                16.072
## poutcomesuccess    11.747
## contactunknown     11.409
## campaign           10.552
## pdays               9.479
## monthmar            6.138
## previous            5.861
## monthmay            4.338
## housingyes          4.090
## monthjul            3.699
## maritalmarried      2.802
## jobstudent          2.547
## jobblue-collar      2.406
## educationtertiary   2.396
## poutcomeunknown     2.393
## loanyes             2.266
## monthjun            2.089

Variable paling penting dalam pembuatan random forest adalah duration.

Prediction

Lakukan prediksi kelas ke data_test dan simpan ke objek forest_class

forest_class <- predict(model_forest, data_test, type = "raw")
head(forest_class)
## [1] no  no  no  no  yes no 
## Levels: no yes

Evaluation

Buat Confusion Matrix dan evaluasi performa model di data test

confusionMatrix(data = forest_class, reference = data_test$y, positive = "yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  no yes
##        no  626  16
##        yes 184  79
##                                           
##                Accuracy : 0.779           
##                  95% CI : (0.7505, 0.8056)
##     No Information Rate : 0.895           
##     P-Value [Acc > NIR] : 1               
##                                           
##                   Kappa : 0.3395          
##                                           
##  Mcnemar's Test P-Value : <2e-16          
##                                           
##             Sensitivity : 0.83158         
##             Specificity : 0.77284         
##          Pos Pred Value : 0.30038         
##          Neg Pred Value : 0.97508         
##              Prevalence : 0.10497         
##          Detection Rate : 0.08729         
##    Detection Prevalence : 0.29061         
##       Balanced Accuracy : 0.80221         
##                                           
##        'Positive' Class : yes             
## 

Kesimpulan

Pada kasus ini kita akan mengunggulkan metrics recall/Sensitivity karena kita berharap akan banyak customer yang akan berlangganan deposito bank

Berikut ini hasil performa pada uji coba data test di metrics recall/Sensitivity untuk setiap model :

  • Naive Bayes : 0.71579
  • Decision Tree : 0.74737
  • Random Forest : 0.83158

Berdasarkan hasil performa di data test, model Random Forest yang menghasilkan metrics evaluasi dengan nilai paling baik

Mari lihat pula apakah model dapat mengklasifikasi kedua kelas dengan lebih baik menggunakan auc value.

forestProb <- predict(model_forest, data_test, type = "prob")
head(forestProb)
##       no   yes
## 1  0.824 0.176
## 5  0.910 0.090
## 8  0.974 0.026
## 9  0.992 0.008
## 13 0.282 0.718
## 21 0.754 0.246
forest_auc <- prediction(predictions = forestProb[, 2],
                        labels = as.numeric(data_test$y == "yes")) 

auc <- performance(prediction.obj = forest_auc, 
                   measure = "auc")
auc@y.values
## [[1]]
## [1] 0.8710006

Model random forest dapat cukup baik mengklasifikasikan kelas positif dan kelas negatif

Dari 3 macam modelling yang telah kita buat dan uji ke data test, kita ketahui bahwa model Random Forest memiliki hasil performa yang paling baik. Namun dari model tersebut kita tidak bisa langsung menginterpretasinya. Jika ingin hasil yang mudah diinterpretasi maka kita akan memilih model Decision Tree