Prediction of customer Deposit at the Bank
Explanation
Permulaan
Hallo ini adalah Rpubs ke-enam saya mengenai Supervised Learning Dengan Metode Klasifikasi, semoga bermanfaat :)
Tentang Data
Dataset ini adalah terkait marketing campaign pada Bank Portugis
Beberapa kolom yang terdapat di dalam nya :
- age : umur
- job : jenis pekerjaan (categorical: ‘admin.’,‘blue-collar’,‘entrepreneur’,‘housemaid’,‘management’,‘retired’,‘self-employed’,‘services’,‘student’,‘technician’,‘unemployed’,‘unknown’)
- marital : status pernikahan (categorical: ‘divorced’,‘married’,‘single’,‘unknown’; note: ‘divorced’ means divorced or widowed)
- education : pendidikan (categorical: ‘basic.4y’,‘basic.6y’,‘basic.9y’,‘high.school’,‘illiterate’,‘professional.course’,‘university.degree’,‘unknown’)
- default: terdapat gagal bayar / tidak (categorical: ‘no’,‘yes’,‘unknown’)
- housing: terdapat pinjaman rumah / tidak (categorical: ‘no’,‘yes’,‘unknown’)
- loan: terdapat pinjaman / tidak (categorical: ‘no’,‘yes’,‘unknown’)
- contact: jenis kontak komunikasi (categorical: ‘cellular’,‘telephone’)
- month: kontak terakhir bulan tahun (categorical: ‘jan’, ‘feb’, ‘mar’, …, ‘nov’, ‘dec’)
- day_of_week: hari kontak terakhir dalam seminggu (categorical: ‘mon’,‘tue’,‘wed’,‘thu’,‘fri’)
- duration: durasi kontak terakhir, dalam detik (numeric).
- campaign: jumlah kontak yang dilakukan selama kampanye ini dan untuk klien ini (numerik, termasuk kontak terakhir)
- pdays:jumlah hari yang berlalu setelah klien terakhir dihubungi dari kampanye sebelumnya (angka; 999 berarti klien tidak pernah dihubungi sebelumnya)
- previous: jumlah kontak yang dilakukan sebelum kampanye ini dan untuk klien ini (numerik)
- poutcome: hasil dari kampanye pemasaran sebelumnya (kategoris: ‘gagal’, ‘tidak ada’, ‘sukses’)
Business Goal
Disini saya membuat Role-Play yang dimana saya adalah pekerja pada suatu Bank di Indonesia. Kemudian saya dan pihak business ingin melakukan analisa terhadap minat seorang nasabah di dalam melakukan Deposit, sehingga kita dapat membuat suatu strategi campaign yang mampu menaikan minat nasabah untuk melakukan Deposit. Di samping itu juga kita ingin membuat sebuah Model Machine Learning yang dapat memprediksi apakah seorang akan melakukan Deposit atau tidak.
Setup Library
kita disini akan membutuhkan beberapa library baik itu untuk visualisasi maupun pembentukan model
library(tidyverse)
library(ggpubr)
library(e1071)
library(caret)
library(ROCR)
library(plotly)
library(partykit)Data Wrangling
Read data terlebih dahulu
bank <- read.csv("data_input/bank.csv", stringsAsFactors = T)Cek struktur data
str(bank)#> 'data.frame': 11162 obs. of 17 variables:
#> $ age : int 59 56 41 55 54 42 56 60 37 28 ...
#> $ job : Factor w/ 12 levels "admin.","blue-collar",..: 1 1 10 8 1 5 5 6 10 8 ...
#> $ marital : Factor w/ 3 levels "divorced","married",..: 2 2 2 2 2 3 2 1 2 3 ...
#> $ education: Factor w/ 4 levels "primary","secondary",..: 2 2 2 2 3 3 3 2 2 2 ...
#> $ default : Factor w/ 2 levels "no","yes": 1 1 1 1 1 1 1 1 1 1 ...
#> $ balance : int 2343 45 1270 2476 184 0 830 545 1 5090 ...
#> $ housing : Factor w/ 2 levels "no","yes": 2 1 2 2 1 2 2 2 2 2 ...
#> $ loan : Factor w/ 2 levels "no","yes": 1 1 1 1 1 2 2 1 1 1 ...
#> $ contact : Factor w/ 3 levels "cellular","telephone",..: 3 3 3 3 3 3 3 3 3 3 ...
#> $ day : int 5 5 5 5 5 5 6 6 6 6 ...
#> $ month : Factor w/ 12 levels "apr","aug","dec",..: 9 9 9 9 9 9 9 9 9 9 ...
#> $ duration : int 1042 1467 1389 579 673 562 1201 1030 608 1297 ...
#> $ campaign : int 1 1 1 1 2 2 1 1 1 3 ...
#> $ pdays : int -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 ...
#> $ previous : int 0 0 0 0 0 0 0 0 0 0 ...
#> $ poutcome : Factor w/ 4 levels "failure","other",..: 4 4 4 4 4 4 4 4 4 4 ...
#> $ deposit : Factor w/ 2 levels "no","yes": 2 2 2 2 2 2 2 2 2 2 ...
Cek missing value
anyNA(bank)#> [1] FALSE
Cek proporsi kelas target
prop.table(table(bank$deposit))#>
#> no yes
#> 0.5261602 0.4738398
ggplot(bank, aes(x = deposit)) +
geom_histogram(
stat = "count",
colour = "black",
aes(fill = deposit),
show.legend = F
) +
labs(x = "Deposit", y = "Freq", title = "Proporsi kelas Target") +
theme_minimal() Dapat dilihat proporsi kelas cukup balance yang dimana terlihat selish kelas Positif (yes) dan Negatif (no) terpaut cukup kecil / tidak signifikan extreme berbeda. Jadi saya rasa kita tidak perlu melakukan downSampling / upSampling
Exploratory Data Analysis
Kita akan coba melakukan analisa terkait keberhasilan dan ketidak-berhasilan suatu marketing campaign pada Data Bank Kita yang dimana akan membuat nasabah melakukan Deposit atau tidak.
Analisa keberhasilan Nasabah melakukan Deposit atau tidak berdasarkan variable Prediktor Age (Umur)
freq_age <- bank %>%
select(age, deposit) %>%
table %>%
as.data.frame
freq_age_yes <- freq_age %>%
filter(deposit == "yes") %>%
arrange(deposit, desc(Freq))
freq_age_no <- freq_age %>%
filter(deposit == "no") %>%
arrange(deposit, desc(Freq))#> List of 93
#> $ line :List of 6
#> ..$ colour : chr "black"
#> ..$ size : num 0.5
#> ..$ linetype : num 1
#> ..$ lineend : chr "butt"
#> ..$ arrow : logi FALSE
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_line" "element"
#> $ rect :List of 5
#> ..$ fill : chr "white"
#> ..$ colour : chr "black"
#> ..$ size : num 0.5
#> ..$ linetype : num 1
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_rect" "element"
#> $ text :List of 11
#> ..$ family : chr ""
#> ..$ face : chr "plain"
#> ..$ colour : chr "black"
#> ..$ size : num 11
#> ..$ hjust : num 0.5
#> ..$ vjust : num 0.5
#> ..$ angle : num 0
#> ..$ lineheight : num 0.9
#> ..$ margin : 'margin' num [1:4] 0points 0points 0points 0points
#> .. ..- attr(*, "unit")= int 8
#> ..$ debug : logi FALSE
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ title : NULL
#> $ aspect.ratio : NULL
#> $ axis.title : NULL
#> $ axis.title.x :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : NULL
#> ..$ size : NULL
#> ..$ hjust : NULL
#> ..$ vjust : num 1
#> ..$ angle : NULL
#> ..$ lineheight : NULL
#> ..$ margin : 'margin' num [1:4] 2.75points 0points 0points 0points
#> .. ..- attr(*, "unit")= int 8
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ axis.title.x.top :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : NULL
#> ..$ size : NULL
#> ..$ hjust : NULL
#> ..$ vjust : num 0
#> ..$ angle : NULL
#> ..$ lineheight : NULL
#> ..$ margin : 'margin' num [1:4] 0points 0points 2.75points 0points
#> .. ..- attr(*, "unit")= int 8
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ axis.title.x.bottom : NULL
#> $ axis.title.y :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : NULL
#> ..$ size : NULL
#> ..$ hjust : NULL
#> ..$ vjust : num 1
#> ..$ angle : num 90
#> ..$ lineheight : NULL
#> ..$ margin : 'margin' num [1:4] 0points 2.75points 0points 0points
#> .. ..- attr(*, "unit")= int 8
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ axis.title.y.left : NULL
#> $ axis.title.y.right :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : NULL
#> ..$ size : NULL
#> ..$ hjust : NULL
#> ..$ vjust : num 0
#> ..$ angle : num -90
#> ..$ lineheight : NULL
#> ..$ margin : 'margin' num [1:4] 0points 0points 0points 2.75points
#> .. ..- attr(*, "unit")= int 8
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ axis.text :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : chr "grey30"
#> ..$ size : 'rel' num 0.8
#> ..$ hjust : NULL
#> ..$ vjust : NULL
#> ..$ angle : NULL
#> ..$ lineheight : NULL
#> ..$ margin : NULL
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ axis.text.x :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : NULL
#> ..$ size : NULL
#> ..$ hjust : NULL
#> ..$ vjust : num 1
#> ..$ angle : NULL
#> ..$ lineheight : NULL
#> ..$ margin : 'margin' num [1:4] 2.2points 0points 0points 0points
#> .. ..- attr(*, "unit")= int 8
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ axis.text.x.top :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : NULL
#> ..$ size : NULL
#> ..$ hjust : NULL
#> ..$ vjust : num 0
#> ..$ angle : NULL
#> ..$ lineheight : NULL
#> ..$ margin : 'margin' num [1:4] 0points 0points 2.2points 0points
#> .. ..- attr(*, "unit")= int 8
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ axis.text.x.bottom : NULL
#> $ axis.text.y :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : NULL
#> ..$ size : NULL
#> ..$ hjust : num 1
#> ..$ vjust : NULL
#> ..$ angle : NULL
#> ..$ lineheight : NULL
#> ..$ margin : 'margin' num [1:4] 0points 2.2points 0points 0points
#> .. ..- attr(*, "unit")= int 8
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ axis.text.y.left : NULL
#> $ axis.text.y.right :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : NULL
#> ..$ size : NULL
#> ..$ hjust : num 0
#> ..$ vjust : NULL
#> ..$ angle : NULL
#> ..$ lineheight : NULL
#> ..$ margin : 'margin' num [1:4] 0points 0points 0points 2.2points
#> .. ..- attr(*, "unit")= int 8
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ axis.ticks : list()
#> ..- attr(*, "class")= chr [1:2] "element_blank" "element"
#> $ axis.ticks.x : NULL
#> $ axis.ticks.x.top : NULL
#> $ axis.ticks.x.bottom : NULL
#> $ axis.ticks.y : NULL
#> $ axis.ticks.y.left : NULL
#> $ axis.ticks.y.right : NULL
#> $ axis.ticks.length : 'simpleUnit' num 2.75points
#> ..- attr(*, "unit")= int 8
#> $ axis.ticks.length.x : NULL
#> $ axis.ticks.length.x.top : NULL
#> $ axis.ticks.length.x.bottom: NULL
#> $ axis.ticks.length.y : NULL
#> $ axis.ticks.length.y.left : NULL
#> $ axis.ticks.length.y.right : NULL
#> $ axis.line : list()
#> ..- attr(*, "class")= chr [1:2] "element_blank" "element"
#> $ axis.line.x : NULL
#> $ axis.line.x.top : NULL
#> $ axis.line.x.bottom : NULL
#> $ axis.line.y : NULL
#> $ axis.line.y.left : NULL
#> $ axis.line.y.right : NULL
#> $ legend.background : list()
#> ..- attr(*, "class")= chr [1:2] "element_blank" "element"
#> $ legend.margin : 'margin' num [1:4] 5.5points 5.5points 5.5points 5.5points
#> ..- attr(*, "unit")= int 8
#> $ legend.spacing : 'simpleUnit' num 11points
#> ..- attr(*, "unit")= int 8
#> $ legend.spacing.x : NULL
#> $ legend.spacing.y : NULL
#> $ legend.key : list()
#> ..- attr(*, "class")= chr [1:2] "element_blank" "element"
#> $ legend.key.size : 'simpleUnit' num 1.2lines
#> ..- attr(*, "unit")= int 3
#> $ legend.key.height : NULL
#> $ legend.key.width : NULL
#> $ legend.text :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : NULL
#> ..$ size : 'rel' num 0.8
#> ..$ hjust : NULL
#> ..$ vjust : NULL
#> ..$ angle : NULL
#> ..$ lineheight : NULL
#> ..$ margin : NULL
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ legend.text.align : NULL
#> $ legend.title :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : NULL
#> ..$ size : NULL
#> ..$ hjust : num 0
#> ..$ vjust : NULL
#> ..$ angle : NULL
#> ..$ lineheight : NULL
#> ..$ margin : NULL
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ legend.title.align : NULL
#> $ legend.position : chr "right"
#> $ legend.direction : NULL
#> $ legend.justification : chr "center"
#> $ legend.box : NULL
#> $ legend.box.just : NULL
#> $ legend.box.margin : 'margin' num [1:4] 0cm 0cm 0cm 0cm
#> ..- attr(*, "unit")= int 1
#> $ legend.box.background : list()
#> ..- attr(*, "class")= chr [1:2] "element_blank" "element"
#> $ legend.box.spacing : 'simpleUnit' num 11points
#> ..- attr(*, "unit")= int 8
#> $ panel.background : list()
#> ..- attr(*, "class")= chr [1:2] "element_blank" "element"
#> $ panel.border : list()
#> ..- attr(*, "class")= chr [1:2] "element_blank" "element"
#> $ panel.spacing : 'simpleUnit' num 5.5points
#> ..- attr(*, "unit")= int 8
#> $ panel.spacing.x : NULL
#> $ panel.spacing.y : NULL
#> $ panel.grid :List of 6
#> ..$ colour : chr "grey92"
#> ..$ size : NULL
#> ..$ linetype : NULL
#> ..$ lineend : NULL
#> ..$ arrow : logi FALSE
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_line" "element"
#> $ panel.grid.major : NULL
#> $ panel.grid.minor :List of 6
#> ..$ colour : NULL
#> ..$ size : 'rel' num 0.5
#> ..$ linetype : NULL
#> ..$ lineend : NULL
#> ..$ arrow : logi FALSE
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_line" "element"
#> $ panel.grid.major.x : NULL
#> $ panel.grid.major.y : NULL
#> $ panel.grid.minor.x : NULL
#> $ panel.grid.minor.y : NULL
#> $ panel.ontop : logi FALSE
#> $ plot.background : list()
#> ..- attr(*, "class")= chr [1:2] "element_blank" "element"
#> $ plot.title :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : NULL
#> ..$ size : 'rel' num 1.2
#> ..$ hjust : num 0
#> ..$ vjust : num 1
#> ..$ angle : NULL
#> ..$ lineheight : NULL
#> ..$ margin : 'margin' num [1:4] 0points 0points 5.5points 0points
#> .. ..- attr(*, "unit")= int 8
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ plot.title.position : chr "panel"
#> $ plot.subtitle :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : NULL
#> ..$ size : NULL
#> ..$ hjust : num 0
#> ..$ vjust : num 1
#> ..$ angle : NULL
#> ..$ lineheight : NULL
#> ..$ margin : 'margin' num [1:4] 0points 0points 5.5points 0points
#> .. ..- attr(*, "unit")= int 8
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ plot.caption :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : NULL
#> ..$ size : 'rel' num 0.8
#> ..$ hjust : num 1
#> ..$ vjust : num 1
#> ..$ angle : NULL
#> ..$ lineheight : NULL
#> ..$ margin : 'margin' num [1:4] 5.5points 0points 0points 0points
#> .. ..- attr(*, "unit")= int 8
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ plot.caption.position : chr "panel"
#> $ plot.tag :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : NULL
#> ..$ size : 'rel' num 1.2
#> ..$ hjust : num 0.5
#> ..$ vjust : num 0.5
#> ..$ angle : NULL
#> ..$ lineheight : NULL
#> ..$ margin : NULL
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ plot.tag.position : chr "topleft"
#> $ plot.margin : 'margin' num [1:4] 5.5points 5.5points 5.5points 5.5points
#> ..- attr(*, "unit")= int 8
#> $ strip.background : list()
#> ..- attr(*, "class")= chr [1:2] "element_blank" "element"
#> $ strip.background.x : NULL
#> $ strip.background.y : NULL
#> $ strip.placement : chr "inside"
#> $ strip.text :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : chr "grey10"
#> ..$ size : 'rel' num 0.8
#> ..$ hjust : NULL
#> ..$ vjust : NULL
#> ..$ angle : NULL
#> ..$ lineheight : NULL
#> ..$ margin : 'margin' num [1:4] 4.4points 4.4points 4.4points 4.4points
#> .. ..- attr(*, "unit")= int 8
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ strip.text.x : NULL
#> $ strip.text.y :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : NULL
#> ..$ size : NULL
#> ..$ hjust : NULL
#> ..$ vjust : NULL
#> ..$ angle : num -90
#> ..$ lineheight : NULL
#> ..$ margin : NULL
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> $ strip.switch.pad.grid : 'simpleUnit' num 2.75points
#> ..- attr(*, "unit")= int 8
#> $ strip.switch.pad.wrap : 'simpleUnit' num 2.75points
#> ..- attr(*, "unit")= int 8
#> $ strip.text.y.left :List of 11
#> ..$ family : NULL
#> ..$ face : NULL
#> ..$ colour : NULL
#> ..$ size : NULL
#> ..$ hjust : NULL
#> ..$ vjust : NULL
#> ..$ angle : num 90
#> ..$ lineheight : NULL
#> ..$ margin : NULL
#> ..$ debug : NULL
#> ..$ inherit.blank: logi TRUE
#> ..- attr(*, "class")= chr [1:2] "element_text" "element"
#> - attr(*, "class")= chr [1:2] "theme" "gg"
#> - attr(*, "complete")= logi TRUE
#> - attr(*, "validate")= logi TRUE
# gabungkan ke-empat plot
ggarrange(p_one, p_two, p_three, p_four,
ncol = 2, nrow = 2,
labels = c("A", "B", "C", "D"))Dari plot diatas kita dapat simpulkan beberapa hal: - Dapat di lihat dari plot A dan B, yang dimana banyak nasabah yang melakukan Deposit berada kebanyakan rentang umur 28 - 37 dan bagi nasabah yang telah berumur (tua), cendrung sedikit di dalam melakukan Deposit - Kemudian dari plot C dan D. Kebanyakan nasabah yang tidak melakukan Deposit berada pada rentang umur 30 - 40 dan kembali pada nasabah yang telah berumur, lebih memilih untuk tidak melakukan Deposit. Dari sini dapat kita simpulkan rata-rata nasabah yang melakukan Deposit berada pada rentang umur yang mudah, dan nasabah yang tidak melakukan Deposit berada pada umur yang tergolong tua. Ini masuk akal jika mengingat banyak Kawula Muda yang tentu ingin mempersiapkan masa tua nya dengan melakukan Deposito sebagai salah satu bentuk investasi. Dan bagi nasabah yang telah berumur cendrung untuk tidak melakukan Deposito mengingat fungsi Deposito sebagai tabungan jangka panjang yang penantian nya tidak sesuai lagi dengan umur mereka
Kemudian mari kita lakukan analisa berdasarkan Variable predikto Marital (Pernikahan)
freq_marital <- bank %>%
select(marital, deposit) %>%
table %>%
as.data.frame
ggplot(freq_marital, aes(x = marital, y = Freq, fill = deposit)) + geom_col(aes(fill = deposit), position ="dodge") +
labs(title = "Frekuensi Deposito Berdasarkan Status Pernikahan", y = "Freq", x = "Marital") +
theme_minimal() +
labs(x = NULL, fill = "Deposit") +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.subtitle = element_text(hjust = 0.5)
)Terlihat dari grafik diatas. Status Pernikahan Single lebih cendrung memilih untuk melakuakn Deposito di bandingkan yang telah menikah apalagi yang bercerai. Ini masuk akal jika kita melihat kehidupan seseorang yang Single lebih memiliki pengeluaran yang tergolong sedikit, sehingga lebih memungkinkan untuk melakukan Deposito di bandingkan yang telah menikah dan bercerai
Mari lanjutkan analisa terhadap variable prediktor Default (Gagal bayar pinjaman)
freq_default <- bank %>%
select(default , deposit) %>%
table %>%
as.data.frame
ggplot(freq_default, aes(x = default, y = Freq, fill = deposit)) + geom_col(aes(fill = deposit), position ="dodge") +
labs(title = "Freq Deposit by Default", y = "Freq", x = "Deposit") +
theme_minimal() +
labs(fill = "Deposit") +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.subtitle = element_text(hjust = 0.5)
) Terlihat dari plot di atas. Nasabah yang sudah pernah default (Gagal membayar pinjaman) lebih cendrung sedikit melakukan Deposito. Tetapi nasabah yang tidak pernah Default, lebih memiliki kemungkinan untuk melakukan Deposito walaupun lebih banyak yang tidak melakukan Deposito.
Terakhir kita akan melakukan analisa terhadap variable prediktor Loan (Pinjaman)
freq_loan <- bank %>%
select(loan, deposit) %>%
table %>%
as.data.frame
ggplot(freq_loan, aes(x = loan, y = Freq, fill = deposit)) + geom_col(aes(fill = deposit), position ="dodge") +
labs(title = "Freq Deposit by Loan", y = "Freq", x = "Loan") +
theme_minimal() +
labs(fill = "Deposit") +
theme(
plot.title = element_text(hjust = 0.5, face = "bold"),
plot.subtitle = element_text(hjust = 0.5),
) Sesuai dugaan. Nasabah yang memiliki pinjaman secara logika nya sudah pasti tidak akan melakukan Deposito, terlihat jelas pada plot diatas. Namun untuk Nasabah yang tidak memiliki pinjaman, cukup banyak yang melakukan Deposito di samping yang tidak melakukan Deposito.
Mari kita cek korelasi antar variable Numerik
# Matrix Correlation
library(GGally)
bank2 <- read.csv("data_input/bank.csv")
ggcorr(
bank2,
hjust = 1,
layout.exp = 2,
label = T,
label_size = 2.9
) Terlihat diatas tidak terdapat variable yang memiliki korelasi kuat antar prediktor nya. Tetapi pada kasus kita kali ini, hal itu tidak penting karena Model yang akan kita gunakan semuanya bebas asumsi.
Cross Validation
# Cross Validation
set.seed(100)
index <- sample(x = nrow(bank), size= nrow(bank)*0.8)
bank_train <- bank[index,] # subsetting data berdasarkan index data yang ada di variabel index
bank_test <- bank[-index,]
prop.table(table(bank_train$deposit))#>
#> no yes
#> 0.5244708 0.4755292
Terlihat Proporsi target variable nya cukup seimbang, tidak berbeda secara signifikan extreme
Naive Bayes
Naive Bayes adalah suatu algoritma klasifikasi yang didasari oleh Bayes’ Theorem of Probability. algoritma ini sendiri sering di jadikan sebagai Base Model / Benchmark Model guna membandingkan dengan algoritma kompleks lainnya karena model ini cukup Robust yang menganggap semua prediktor sama penting (semua prediktor independen) dan semua prediktor berpengaruh juga terhadap variable target serta komputasi model yang lebih ringan.
Mari kita latih Model Naive Bayes kita menggunakan Data Train yang sudah kita buat tadi
model_bank <- naiveBayes(deposit ~ ., data = bank_train, laplace = 1)# model interpretasi
model_bank#>
#> Naive Bayes Classifier for Discrete Predictors
#>
#> Call:
#> naiveBayes.default(x = X, y = Y, laplace = laplace)
#>
#> A-priori probabilities:
#> Y
#> no yes
#> 0.5244708 0.4755292
#>
#> Conditional probabilities:
#> age
#> Y [,1] [,2]
#> no 40.92227 10.24666
#> yes 41.69336 13.52419
#>
#> job
#> Y admin. blue-collar entrepreneur housemaid management retired
#> no 0.122896699 0.207028754 0.033439830 0.029392971 0.215761448 0.043024494
#> yes 0.119069986 0.134335369 0.023954908 0.019492720 0.244246125 0.097698450
#> job
#> Y self-employed services student technician unemployed unknown
#> no 0.039829606 0.089456869 0.016187433 0.169542066 0.027263046 0.006176784
#> yes 0.035697511 0.071395021 0.048849225 0.159934241 0.038046031 0.007280413
#>
#> marital
#> Y divorced married single
#> no 0.1131029 0.6148101 0.2720871
#> yes 0.1200282 0.5212991 0.3586726
#>
#> education
#> Y primary secondary tertiary unknown
#> no 0.15510988 0.51845530 0.28376360 0.04267122
#> yes 0.11552941 0.46305882 0.37552941 0.04588235
#>
#> default
#> Y no yes
#> no 0.97972252 0.02027748
#> yes 0.98917137 0.01082863
#>
#> balance
#> Y [,1] [,2]
#> no 1271.344 2830.382
#> yes 1847.275 3708.306
#>
#> housing
#> Y no yes
#> no 0.4264674 0.5735326
#> yes 0.6358286 0.3641714
#>
#> loan
#> Y no yes
#> no 0.83415155 0.16584845
#> yes 0.90536723 0.09463277
#>
#> contact
#> Y cellular telephone unknown
#> no 0.62654716 0.06572770 0.30772514
#> yes 0.82019299 0.07460579 0.10520122
#>
#> day
#> Y [,1] [,2]
#> no 16.11232 8.361403
#> yes 15.26354 8.525471
#>
#> month
#> Y apr aug dec feb jan jul
#> no 0.060063898 0.141640043 0.001916933 0.057933972 0.034291800 0.150798722
#> yes 0.113668389 0.126820103 0.019023016 0.079379991 0.025364021 0.118835134
#> month
#> Y jun mar may nov oct sep
#> no 0.114164004 0.004685836 0.320340788 0.094142705 0.011075612 0.008945687
#> yes 0.104039455 0.046500705 0.176843589 0.077736026 0.062470643 0.049318929
#>
#> duration
#> Y [,1] [,2]
#> no 224.372 211.0635
#> yes 541.334 395.6650
#>
#> campaign
#> Y [,1] [,2]
#> no 2.826607 3.164951
#> yes 2.157560 1.954102
#>
#> pdays
#> Y [,1] [,2]
#> no 35.63741 95.94072
#> yes 68.21267 118.42614
#>
#> previous
#> Y [,1] [,2]
#> no 0.512492 1.873967
#> yes 1.166981 2.594395
#>
#> poutcome
#> Y failure other success unknown
#> no 0.10646469 0.03819074 0.01642842 0.83891615
#> yes 0.11576471 0.05882353 0.18282353 0.64258824
Dari hasil model Naive Bayes kita, dapat kita interpretasikan beberapa hal di antaranya: - Nasabah yang tidak memiliki pinjaman berpeluang 90% untuk melakukan Deposito - Nasabah yang tidak pernah gagal bayar pinjaman berpeluang 98% untuk melakukan Deposito - Dari sekian banyak pekerjaan (job) dari para nasabah. Yang paling besar peluang nya untuk melakukan Deposito dari pada tidak melakukan nya ialah Admin (11%), Manajemen (24%) dan Teknisi (15%)
Mari kita lakukan Prediksi dengan Model yang telah kita buat menggunakan Data Test yang telah kita buat sebelumnya
# model prediksi
bank_predClass <- predict(object = model_bank, newdata = bank_test, type = "class")
head(data.frame(Prediksi = bank_predClass, actual = bank_test$deposit))#> Prediksi actual
#> 1 yes yes
#> 2 no yes
#> 3 no yes
#> 4 yes yes
#> 5 no yes
#> 6 yes yes
Untuk menguji Performansi dari pada model kita, mari gunakan Confusion Matrix
conf_naive <- confusionMatrix(data = bank_predClass, reference = bank_test$deposit, positive = "yes")
conf_naive#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction no yes
#> no 962 276
#> yes 228 767
#>
#> Accuracy : 0.7743
#> 95% CI : (0.7564, 0.7915)
#> No Information Rate : 0.5329
#> P-Value [Acc > NIR] : <0.0000000000000002
#>
#> Kappa : 0.5453
#>
#> Mcnemar's Test P-Value : 0.0363
#>
#> Sensitivity : 0.7354
#> Specificity : 0.8084
#> Pos Pred Value : 0.7709
#> Neg Pred Value : 0.7771
#> Prevalence : 0.4671
#> Detection Rate : 0.3435
#> Detection Prevalence : 0.4456
#> Balanced Accuracy : 0.7719
#>
#> 'Positive' Class : yes
#>
Dari hasil Confusion Matrix diatas dapat kita simpulkan bahwa: - Recall / Sensitivity => Yang benar di prediksi positif dari yang realita nya (aktual nya) positif oleh Model kita ada 73% - Specifity => Yang benar di prediksi negatif dari yang realita nya (aktual nya) negatif oleh Model kita ada 80% - Precission Yang benar di prediksi positif dari yang di prediksi nya - positif oleh Model kita ada 77% - Accuracy => Seberapa banyak yang benar di prediksi dari keseluruhan data (positif maupun negatif) oleh model kita ada 77%
Kemudian kita akan melihat kualitas model kita melalui Plot ROC dan nilai AUC nya
# roc dan auc
# buat prediksi dalam bentuk probabilitas
bank_prob <- predict(object = model_bank, newdata = bank_test, type = "raw")
data_roc <- data.frame(pred_proba = bank_prob[, "yes"],
actual = ifelse(bank_test$deposit == "yes" ,1,0))
# buat objek prediction
roc_pred <- prediction(predictions = data_roc$pred_proba, labels = data_roc$actual)
# buat plot dari objek prediction
plot(performance(prediction.obj = roc_pred, measure = "tpr", x.measure = "fpr"))auc_value <- performance(prediction.obj = roc_pred, measure = "auc")
auc_value@y.values # untuk menampilkan nilai AUC dari Plot ROC#> [[1]]
#> [1] 0.8501478
Di lihat dari plot ROC nya, untuk False Positive Rate nya sudah mendakati 0, yan dimana itu artinya Kesalahn model kita di dalam memprediksi sudah semakin sedikit / kecil. Kemudian terkait True Positive Rate nya sudah mendekati 1, yang artinya model kita sudah semakin baik didalam memprediksi kelas Positif maupun Negatif. Di lihat dari nilai AUC nya pun sudah mendakati 1 (85%), dengan kata lain performansi model kita sudah semakin baik di dalam mengklasifikasikan kelas Positif maupun Negatif.
Dari hasil model kita diatas, sebenarnya kita masih bisa melakukan Tunning Model menggunakan perubahan Threshold. Mari kita coba memvisualisasikan hasil pergeseran Threshold yang akan meningkatkan Value ke-empat metrix kita (Accuracy, Precision, Recall dan Specifity)
predict_prob <- data.frame(negative = round(bank_prob[,1],4), positive = round(bank_prob[,2],4))# model tuning - metrics function
metrics <- function(cutoff, prob, ref, postarget, negtarget)
{
predict <- factor(ifelse(prob >= cutoff, postarget, negtarget))
conf <- caret::confusionMatrix(predict , ref, positive = postarget)
acc <- conf$overall[1]
rec <- conf$byClass[1]
prec <- conf$byClass[3]
spec <- conf$byClass[2]
mat <- t(as.matrix(c(rec , acc , prec, spec)))
colnames(mat) <- c("recall", "accuracy", "precicion", "specificity")
return(mat)
}
co <- seq(0.01,0.99,length=100)
result <- matrix(0,100,4)
# apply function metrics
for(i in 1:100){
result[i,] = metrics(cutoff = co[i],
prob = predict_prob$positive,
ref = as.factor(ifelse(bank_test$deposit == "yes", 1, 0)),
postarget = "1",
negtarget = "0")
}
# visualize
ggplotly(tibble("Recall" = result[,1],
"Accuracy" = result[,2],
"Precision" = result[,3],
"Specificity" = result[,4],
"Cutoff" = co) %>%
gather(key = "Metrics", value = "value", 1:4) %>%
ggplot(aes(x = Cutoff, y = value, col = Metrics)) +
geom_line(lwd = 1.5) +
scale_color_manual(values = c("darkred","darkgreen","orange", "blue")) +
scale_y_continuous(breaks = seq(0,1,0.1), limits = c(0,1)) +
scale_x_continuous(breaks = seq(0,1,0.1)) +
labs(title = "Tradeoff Model Perfomance") +
theme_minimal() +
theme(legend.position = "top",
panel.grid.minor.y = element_blank(),
panel.grid.minor.x = element_blank()))Sepertinya Threshold default (0.5) sudah membuat model kita memiliki nilai yang cukup baik untuk metrix (Accuracy, Precision, Recall dan Specifity)
Decision Tree
Decision Tree merupakan Model yang cukup banyak di gunakan juga karena dapat di interpretasikan, bisa digunakan untuk prediktor numerik maupun kategorik ,bisa mengatasi outlier dan cukup robust
Mari langsung kita coba membuat Model Decision Tree nya
model_bank_tree <- ctree(formula = deposit ~ ., data = bank_train)
# struktru data pohon
model_bank_tree#>
#> Model formula:
#> deposit ~ age + job + marital + education + default + balance +
#> housing + loan + contact + day + month + duration + campaign +
#> pdays + previous + poutcome
#>
#> Fitted party:
#> [1] root
#> | [2] duration <= 206
#> | | [3] month in apr, dec, feb, mar, oct, sep
#> | | | [4] duration <= 77
#> | | | | [5] day <= 20: no (n = 112, err = 7.1%)
#> | | | | [6] day > 20: no (n = 12, err = 50.0%)
#> | | | [7] duration > 77
#> | | | | [8] poutcome in failure, other
#> | | | | | [9] duration <= 124: no (n = 67, err = 19.4%)
#> | | | | | [10] duration > 124
#> | | | | | | [11] housing in no: yes (n = 58, err = 32.8%)
#> | | | | | | [12] housing in yes: no (n = 49, err = 36.7%)
#> | | | | [13] poutcome in success, unknown
#> | | | | | [14] month in apr, feb
#> | | | | | | [15] housing in no
#> | | | | | | | [16] day <= 7: no (n = 63, err = 33.3%)
#> | | | | | | | [17] day > 7: yes (n = 148, err = 15.5%)
#> | | | | | | [18] housing in yes
#> | | | | | | | [19] poutcome in success: yes (n = 12, err = 8.3%)
#> | | | | | | | [20] poutcome in unknown: no (n = 71, err = 26.8%)
#> | | | | | [21] month in dec, mar, oct, sep
#> | | | | | | [22] campaign <= 4: yes (n = 202, err = 12.4%)
#> | | | | | | [23] campaign > 4: no (n = 9, err = 33.3%)
#> | | [24] month in aug, jan, jul, jun, may, nov
#> | | | [25] poutcome in failure, other, unknown
#> | | | | [26] duration <= 127
#> | | | | | [27] job in admin., blue-collar, entrepreneur, housemaid, management, retired, self-employed, services, technician, unknown
#> | | | | | | [28] poutcome in failure, other
#> | | | | | | | [29] month in aug, jan, jul, may, nov
#> | | | | | | | | [30] contact in cellular
#> | | | | | | | | | [31] job in admin., blue-collar, entrepreneur, management, retired, services, technician: no (n = 159, err = 2.5%)
#> | | | | | | | | | [32] job in housemaid, self-employed, unknown: no (n = 10, err = 30.0%)
#> | | | | | | | | [33] contact in telephone, unknown: no (n = 14, err = 14.3%)
#> | | | | | | | [34] month in jun: yes (n = 8, err = 25.0%)
#> | | | | | | [35] poutcome in unknown
#> | | | | | | | [36] marital in divorced, married: no (n = 1031, err = 1.0%)
#> | | | | | | | [37] marital in single: no (n = 307, err = 3.9%)
#> | | | | | [38] job in student, unemployed: no (n = 56, err = 19.6%)
#> | | | | [39] duration > 127
#> | | | | | [40] housing in no
#> | | | | | | [41] poutcome in failure, other: yes (n = 67, err = 35.8%)
#> | | | | | | [42] poutcome in unknown
#> | | | | | | | [43] day <= 5
#> | | | | | | | | [44] contact in cellular, telephone: yes (n = 44, err = 36.4%)
#> | | | | | | | | [45] contact in unknown: no (n = 16, err = 6.2%)
#> | | | | | | | [46] day > 5
#> | | | | | | | | [47] education in primary, secondary
#> | | | | | | | | | [48] job in admin., blue-collar, entrepreneur, housemaid, management, retired, self-employed, services, technician, unknown: no (n = 205, err = 6.8%)
#> | | | | | | | | | [49] job in student, unemployed: no (n = 13, err = 46.2%)
#> | | | | | | | | [50] education in tertiary, unknown
#> | | | | | | | | | [51] month in aug, jul, may: no (n = 121, err = 13.2%)
#> | | | | | | | | | [52] month in jan, jun, nov: no (n = 58, err = 41.4%)
#> | | | | | [53] housing in yes
#> | | | | | | [54] month in aug, jan, jun, nov
#> | | | | | | | [55] contact in cellular, telephone
#> | | | | | | | | [56] month in aug, jan, nov: no (n = 103, err = 14.6%)
#> | | | | | | | | [57] month in jun: yes (n = 9, err = 22.2%)
#> | | | | | | | [58] contact in unknown: no (n = 44, err = 2.3%)
#> | | | | | | [59] month in jul, may
#> | | | | | | | [60] poutcome in failure
#> | | | | | | | | [61] pdays <= 278: no (n = 7, err = 42.9%)
#> | | | | | | | | [62] pdays > 278: no (n = 29, err = 6.9%)
#> | | | | | | | [63] poutcome in other, unknown: no (n = 374, err = 1.6%)
#> | | | [64] poutcome in success
#> | | | | [65] duration <= 101: no (n = 24, err = 33.3%)
#> | | | | [66] duration > 101: yes (n = 105, err = 14.3%)
#> | [67] duration > 206
#> | | [68] duration <= 405
#> | | | [69] poutcome in failure, unknown
#> | | | | [70] month in apr, dec, feb, mar, oct, sep
#> | | | | | [71] housing in no
#> | | | | | | [72] day <= 5
#> | | | | | | | [73] month in apr, dec, mar
#> | | | | | | | | [74] balance <= 1348: yes (n = 19, err = 0.0%)
#> | | | | | | | | [75] balance > 1348: yes (n = 7, err = 28.6%)
#> | | | | | | | [76] month in feb, oct, sep: no (n = 62, err = 41.9%)
#> | | | | | | [77] day > 5: yes (n = 286, err = 13.3%)
#> | | | | | [78] housing in yes
#> | | | | | | [79] month in apr, feb
#> | | | | | | | [80] day <= 20
#> | | | | | | | | [81] month in apr: no (n = 89, err = 15.7%)
#> | | | | | | | | [82] month in feb
#> | | | | | | | | | [83] day <= 9: no (n = 25, err = 20.0%)
#> | | | | | | | | | [84] day > 9: yes (n = 9, err = 0.0%)
#> | | | | | | | [85] day > 20: yes (n = 24, err = 20.8%)
#> | | | | | | [86] month in dec, mar, oct, sep: yes (n = 43, err = 14.0%)
#> | | | | [87] month in aug, jan, jul, jun, may, nov
#> | | | | | [88] contact in cellular, telephone
#> | | | | | | [89] month in aug, jan, jul, may, nov
#> | | | | | | | [90] housing in no
#> | | | | | | | | [91] day <= 16
#> | | | | | | | | | [92] loan in no
#> | | | | | | | | | | [93] job in admin., housemaid, management, retired, services, student, unemployed: yes (n = 161, err = 25.5%)
#> | | | | | | | | | | [94] job in blue-collar, entrepreneur, self-employed, technician, unknown: no (n = 84, err = 40.5%)
#> | | | | | | | | | [95] loan in yes: no (n = 32, err = 18.8%)
#> | | | | | | | | [96] day > 16
#> | | | | | | | | | [97] month in aug, jan, jul, nov
#> | | | | | | | | | | [98] pdays <= -1
#> | | | | | | | | | | | [99] balance <= 1148: no (n = 155, err = 13.5%)
#> | | | | | | | | | | | [100] balance > 1148: no (n = 61, err = 37.7%)
#> | | | | | | | | | | [101] pdays > -1: yes (n = 22, err = 31.8%)
#> | | | | | | | | | [102] month in may: yes (n = 33, err = 24.2%)
#> | | | | | | | [103] housing in yes: no (n = 381, err = 19.7%)
#> | | | | | | [104] month in jun: yes (n = 85, err = 9.4%)
#> | | | | | [105] contact in unknown
#> | | | | | | [106] month in aug, jul, nov: no (n = 9, err = 33.3%)
#> | | | | | | [107] month in jun, may
#> | | | | | | | [108] month in jun: no (n = 124, err = 7.3%)
#> | | | | | | | [109] month in may
#> | | | | | | | | [110] job in admin., blue-collar, management, self-employed, services, student, technician, unemployed: no (n = 258, err = 0.4%)
#> | | | | | | | | [111] job in entrepreneur, retired: no (n = 10, err = 10.0%)
#> | | | [112] poutcome in other, success
#> | | | | [113] poutcome in other
#> | | | | | [114] housing in no: yes (n = 82, err = 15.9%)
#> | | | | | [115] housing in yes: no (n = 48, err = 45.8%)
#> | | | | [116] poutcome in success: yes (n = 388, err = 5.7%)
#> | | [117] duration > 405
#> | | | [118] duration <= 643
#> | | | | [119] contact in cellular, telephone
#> | | | | | [120] month in apr, aug, dec, jun, mar, oct, sep
#> | | | | | | [121] housing in no: yes (n = 341, err = 10.9%)
#> | | | | | | [122] housing in yes: yes (n = 112, err = 25.9%)
#> | | | | | [123] month in feb, jan, jul, may, nov
#> | | | | | | [124] poutcome in failure, other, unknown: yes (n = 538, err = 33.6%)
#> | | | | | | [125] poutcome in success
#> | | | | | | | [126] marital in divorced, married: yes (n = 42, err = 0.0%)
#> | | | | | | | [127] marital in single: yes (n = 14, err = 28.6%)
#> | | | | [128] contact in unknown
#> | | | | | [129] duration <= 472: no (n = 61, err = 23.0%)
#> | | | | | [130] duration > 472: yes (n = 161, err = 46.6%)
#> | | | [131] duration > 643
#> | | | | [132] marital in divorced, single: yes (n = 704, err = 9.8%)
#> | | | | [133] marital in married: yes (n = 852, err = 16.2%)
#>
#> Number of inner nodes: 66
#> Number of terminal nodes: 67
plot(model_bank_tree, type = "simple") Dari hasil model kita diatas, dapat kita simpulkan beberapa point bahwa: - jik poutcome (hasil dari marketing campaign sebelumnya) sukses, maka status pernikaham baik itu divorced, married ataupun single, di prediksi akan melaukan Deposito - jika housing (pinjaman perumahan) tidak ada, makam pekerjaan admin, housemaid, management, retired, services, student, unemployed akan di prediksi melakukan Deposito juga
Mari kita lakukan evaluasi model agar mengtahui performansi dari pada model Decision Tree yang telah kita buat.
# model evaluasi
bank_pred_class <- predict(object = model_bank_tree, newdata = bank_test, type = "response")
conf_tree <- confusionMatrix(data = bank_pred_class,
reference = bank_test$deposit, positive = "yes")
conf_tree#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction no yes
#> no 979 156
#> yes 211 887
#>
#> Accuracy : 0.8356
#> 95% CI : (0.8196, 0.8508)
#> No Information Rate : 0.5329
#> P-Value [Acc > NIR] : < 0.00000000000000022
#>
#> Kappa : 0.6709
#>
#> Mcnemar's Test P-Value : 0.004821
#>
#> Sensitivity : 0.8504
#> Specificity : 0.8227
#> Pos Pred Value : 0.8078
#> Neg Pred Value : 0.8626
#> Prevalence : 0.4671
#> Detection Rate : 0.3972
#> Detection Prevalence : 0.4917
#> Balanced Accuracy : 0.8366
#>
#> 'Positive' Class : yes
#>
Dari hasil model Decision Tree kita di atas, dapat di simpulkan: 1. Recall / Sensitivity => Yang benar di prediksi positif dari yang realita nya (aktual nya) positif oleh Model kita ada 85% 2. Specifity => Yang benar di prediksi negatif dari yang realita nya (aktual nya) negatif oleh Model kita ada 82% 3. Precission Yang benar di prediksi positif dari yang di prediksi nya - positif oleh Model kita ada 80% 4. Accuracy => Seberapa banyak yang benar di prediksi dari keseluruhan data (positif maupun negatif) oleh model kita ada 83%
Sebelum kita melanjutkan pada Tunning Model dengan Threshold, kita akan menguji terlebih dahulu apakah model kita sudah Good Fit atau justru Overfitt. Mari kita uji pada Data Train
# cek pada data train
bank_pred_class_train <- predict(object = model_bank_tree, newdata = bank_train, type = "response")
confusionMatrix(data = bank_pred_class_train,
reference = bank_train$deposit, positive = "yes") #> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction no yes
#> no 3873 480
#> yes 810 3766
#>
#> Accuracy : 0.8555
#> 95% CI : (0.8481, 0.8628)
#> No Information Rate : 0.5245
#> P-Value [Acc > NIR] : < 0.00000000000000022
#>
#> Kappa : 0.7114
#>
#> Mcnemar's Test P-Value : < 0.00000000000000022
#>
#> Sensitivity : 0.8870
#> Specificity : 0.8270
#> Pos Pred Value : 0.8230
#> Neg Pred Value : 0.8897
#> Prevalence : 0.4755
#> Detection Rate : 0.4218
#> Detection Prevalence : 0.5125
#> Balanced Accuracy : 0.8570
#>
#> 'Positive' Class : yes
#>
Terlihat hasil Accuracy prediksi kita terhadap Data Test dan Data Train tidak terpaut jauh. Pada Data Test Accuracy kita 83% dan pada Data Train 85%, itu artinya Accuray pada Data Test dan Data Train masih sangat dekat sehingga tidak tergolong Overfit.
Mari kita coba lihat plot ROC dan nilai AUC nya
# roc dan auc
bank_pred_prob <- predict(object = model_bank_tree, newdata = bank_test, type = "prob")
dtree_roc <- data.frame(pred_proba= bank_pred_prob[, "yes"],
actual=as.factor(ifelse(bank_test$deposit=="yes", 1, 0)))
roc_pred <- prediction(predictions = dtree_roc$pred_proba,
labels = dtree_roc$actual)
# buat plot dari objek prediction
plot(performance(prediction.obj = roc_pred, measure = "tpr", x.measure = "fpr"))auc_value <- performance(prediction.obj = roc_pred, measure = "auc")
auc_value@y.values # untuk menampilkan nilai AUC dari Plot ROC#> [[1]]
#> [1] 0.9034105
Di lihat dari plot ROC nya, untuk False Positive Rate nya sudah mendakati 0, yan dimana itu artinya Kesalahn model kita di dalam memprediksi sudah semakin sedikit / kecil. Kemudian terkait True Positive Rate nya sudah mendekati 1, yang artinya model kita sudah semakin baik didalam memprediksi kelas Positif maupun Negatif. Di lihat dari nilai AUC nya pun sudah mendakati 1 (90%), dengan kata lain performansi model kita sudah semakin baik di dalam mengklasifikasikan kelas Positif maupun Negatif.
Mari kita coba Tunning Model nya dengan Pergeseran Threshold
predict_prob_tree <- data.frame(negative = round(bank_pred_prob[,1],4), positive = round(bank_pred_prob[,2],4))
# apply function metrics
for(i in 1:100){
result[i,] = metrics(cutoff = co[i],
prob = predict_prob_tree$positive,
ref = as.factor(ifelse(bank_test$deposit == "yes", 1, 0)),
postarget = "1",
negtarget = "0")
}
# visualize
ggplotly(tibble("Recall" = result[,1],
"Accuracy" = result[,2],
"Precision" = result[,3],
"Specificity" = result[,4],
"Cutoff" = co) %>%
gather(key = "Metrics", value = "value", 1:4) %>%
ggplot(aes(x = Cutoff, y = value, col = Metrics)) +
geom_line(lwd = 1.5) +
scale_color_manual(values = c("darkred","darkgreen","orange", "blue")) +
scale_y_continuous(breaks = seq(0,1,0.1), limits = c(0,1)) +
scale_x_continuous(breaks = seq(0,1,0.1)) +
labs(title = "Tradeoff Model Perfomance") +
theme_minimal() +
theme(legend.position = "top",
panel.grid.minor.y = element_blank(),
panel.grid.minor.x = element_blank()))Terlihat pada Cutoff / Threshold 0.6 nilai ke-empat metrix cukup meningkat mari kita coba untuk menggunakan Threshold 0.6
# tuning final model
confusionMatrix(data = as.factor(ifelse(bank_pred_prob[, "yes"] > 0.60, "yes", "no")),
reference = bank_test$deposit, positive = "yes") #> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction no yes
#> no 994 167
#> yes 196 876
#>
#> Accuracy : 0.8374
#> 95% CI : (0.8215, 0.8525)
#> No Information Rate : 0.5329
#> P-Value [Acc > NIR] : <0.0000000000000002
#>
#> Kappa : 0.674
#>
#> Mcnemar's Test P-Value : 0.1417
#>
#> Sensitivity : 0.8399
#> Specificity : 0.8353
#> Pos Pred Value : 0.8172
#> Neg Pred Value : 0.8562
#> Prevalence : 0.4671
#> Detection Rate : 0.3923
#> Detection Prevalence : 0.4801
#> Balanced Accuracy : 0.8376
#>
#> 'Positive' Class : yes
#>
Tidak jauh berbeda terkait metrix Accuracy nya, Recall nya pun menurun. Tapi jika di lihat pada Precision nya meningkat 1% dari 80 menjadi 81%. ini kurang bagus karena kita akan memilih metrix Recall pada kasus kali ini dan jika di lihat dari nilai Recall nya jelas turun dari pada model sebelumnya.Tetapi nanti kita lihat hasil di akhir nya setelah kita telah membuat model Random Forest nya.
Random Forest
Random Forest merupakan salah satu model (Ensemble Method) yang memiliki performa sangat baik. Ia juga dapat memilih feature selection secara otomatis dan terdapat juga Out of Bag sample yang digunakan sebagain pengganti Data Test. Jadi kita tek perlu repot-repot untuk membuat data train dan test. Tetapi kekurangan nya ia adalah model black-box yang artinya ia tidak dapat di interpertasikan. Dan Training Cost nya sangat besar (dari hardware maupun waktu).
Mari kita coba lihat dimensi data kita
dim(bank)#> [1] 11162 17
Kita memiliki kolom yang cukup banyak, alangkah baik nya jika kita memfilter mana variable yang memiliki variance tinggi atau banyak informasi yang terkandung di dalam nya. Sehingga kolom dengan variance yang rendah (mendekati 0) akan kita take out, karena tidak mengandung banyak informasi yang berguna di dalam nya. Kita akan menggunakan fungsi nearZeroVar.
library(caret)
no_var <- nearZeroVar(bank)
bank_clean <- bank[, -no_var]
dim(bank_clean)#> [1] 11162 15
Terlihat sekarang kita sudah memiliki 15 kolom dari 17 kolom sebelumnya
Mari kita lakukan Cross Validation, sebenarnya tidak wajib pada random forest melakukan splitting data test dan train, tapi tidak apa-apa juga kalau kita ingin melakukan nya.
set.seed(100)
index_two <- sample(x = nrow(bank_clean), size= nrow(bank_clean)*0.8)
bank_train_rf <- bank_clean[index_two, ]
bank_test_rf <- bank_clean[-index_two,]Kemudian kita disini kita akan menggunakan k-fold validation untuk melakukan cross validation pada model random forest kita. Jadi apa k-fold validation itu ? K-fold validation merupakan teknik Cross Validation yangakan membagi datanya sebanyak k bagian, setiap bagian akan digunakan menjadi data test secara bergantian. Dan pada akhirnya akan di ambil Fold dengan Mtry (prediktor) yang memiliki Accuracy paling tinggi.
Kita disini akan menggunakan 5 K-fold dan di repeat sebanyak 3 kali dengan percobaan Mtry (prediktor) yang berbeda.
# kita akan menggunakan k-fold validation untuk melakukan cross validation pada model random forest kita
set.seed(417)
control <- trainControl(method = "repeatedcv", number = 5, repeats = 3)
# pembuatan model random forest, disini model nya tidak akan saya running karena sudah saya training sebelumya dan telah saya simpan pada Objeck RDS
# model_bank_rf <- train(deposit~., data = bank_train_rf, method = "rf",
# trainControl = control)Mari kita panggil model Random Forest yang sebelunya telah saya latih pada Objek RDS nya.
bank_forest <- readRDS("bank_forest.RDS")Saya disini menggunakan gambar dari pada Output finalModel dari random forest nya di karenakan akan mengeluarkan Output yang sangat panjang jika saya tulis Manual (bank_forest$finalModel) nya
Dapat kita lihat diatas. hasil akhir model random forest kita menggunakan mtry (variable prediktor) sebanyak 21 dan Error dari Out of Bag (data testing) sebesar 14.46% yang artinya Accuracy kita pada data test sebesar (100 - 14.46) = 85%
Mari kita coba untuk mengevaluasi model random forest nya menggunakan Confusion Matrix
bank_forest_pred <- predict(object = bank_forest, newdata = bank_test_rf)conf_forest <- confusionMatrix(data = bank_forest_pred, reference = bank_test_rf$deposit, positive = "yes")
conf_forest#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction no yes
#> no 989 123
#> yes 201 920
#>
#> Accuracy : 0.8549
#> 95% CI : (0.8396, 0.8693)
#> No Information Rate : 0.5329
#> P-Value [Acc > NIR] : < 0.00000000000000022
#>
#> Kappa : 0.7099
#>
#> Mcnemar's Test P-Value : 0.00001888
#>
#> Sensitivity : 0.8821
#> Specificity : 0.8311
#> Pos Pred Value : 0.8207
#> Neg Pred Value : 0.8894
#> Prevalence : 0.4671
#> Detection Rate : 0.4120
#> Detection Prevalence : 0.5020
#> Balanced Accuracy : 0.8566
#>
#> 'Positive' Class : yes
#>
Dari hasil model Decision Tree kita di atas, dapat di simpulkan: - Recall / Sensitivity => Yang benar di prediksi positif dari yang realita nya (aktual nya) positif oleh Model kita ada 88% - Specifity => Yang benar di prediksi negatif dari yang realita nya (aktual nya) negatif oleh Model kita ada 83% - Precission Yang benar di prediksi positif dari yang di prediksi nya - positif oleh Model kita ada 82% - Accuracy => Seberapa banyak yang benar di prediksi dari keseluruhan data (positif maupun negatif) oleh model kita ada 85%
Mari kita coba lihat Plot ROC dan nilai AUC nya
bank_forest_pred_prob <- predict(object = bank_forest, newdata = bank_test_rf, type="prob")
forest_roc <- data.frame(pred_proba= bank_forest_pred_prob[, "yes"],
actual=as.factor(ifelse(bank_test_rf$deposit=="yes", 1, 0)))
roc_pred <- prediction(predictions = forest_roc$pred_proba,
labels = forest_roc$actual)
# buat plot dari objek prediction
plot(performance(prediction.obj = roc_pred, measure = "tpr", x.measure = "fpr"))auc_value <- performance(prediction.obj = roc_pred, measure = "auc")
auc_value@y.values # untuk menampilkan nilai AUC dari Plot ROC#> [[1]]
#> [1] 0.9219277
Di lihat dari plot ROC nya, untuk False Positive Rate nya sudah mendakati 0, yan dimana itu artinya Kesalahn model kita di dalam memprediksi sudah semakin sedikit / kecil. Kemudian terkait True Positive Rate nya sudah mendekati 1, yang artinya model kita sudah semakin baik didalam memprediksi kelas Positif maupun Negatif. Di lihat dari nilai AUC nya pun sudah mendakati 1 (92%), dengan kata lain performansi model kita sudah semakin baik di dalam mengklasifikasikan kelas Positif maupun Negatif.
Mari kita lihat Importance Variable yang terdepat pada model kita. Importance Variable ini dapat di artikan sebagin variable yang dapat mengurangi Error pada model kita melakukan Splitting (pemisahan pada pohon) dan meningkatkan Accuracy pada model kita. Tapi perlu di ingat tidak selamanya Variable Importance ini benar, terkadang bisa juga misleading, jadi di sesuaikan saja dengan kebutuhan bisnis nya, jika varialbe tersebut masuk ke dalam kategori variable yang importance tapi pada kenyataan nya tidak, maka bisa di take out saja. Misalnya seperti ID, sudah jelas ID tidak mungkin kita ikut sertakan di dalam model kita, tapi kalau misalkan di kategori kan kedalam variable importance maka bisa di take out.
varImp(bank_forest) %>% plot() Duration merupakan variable yang paling berpengaruh terhadap model Random Forest kita.
Mari coba lihat posisi threshold terbaik pada model kita
predict_prob_forest <- data.frame(negative = round(bank_forest_pred_prob[,1],4), positive = round(bank_forest_pred_prob[,2],4))
# apply function metrics
for(i in 1:100){
result[i,] = metrics(cutoff = co[i],
prob = predict_prob_forest$positive,
ref = as.factor(ifelse(bank_test_rf$deposit == "yes", 1, 0)),
postarget = "1",
negtarget = "0")
}
# visualize
ggplotly(tibble("Recall" = result[,1],
"Accuracy" = result[,2],
"Precision" = result[,3],
"Specificity" = result[,4],
"Cutoff" = co) %>%
gather(key = "Metrics", value = "value", 1:4) %>%
ggplot(aes(x = Cutoff, y = value, col = Metrics)) +
geom_line(lwd = 1.5) +
scale_color_manual(values = c("darkred","darkgreen","orange", "blue")) +
scale_y_continuous(breaks = seq(0,1,0.1), limits = c(0,1)) +
scale_x_continuous(breaks = seq(0,1,0.1)) +
labs(title = "Tradeoff Model Perfomance") +
theme_minimal() +
theme(legend.position = "top",
panel.grid.minor.y = element_blank(),
panel.grid.minor.x = element_blank()))Seperti nya 0.5 sudah cukup baik untuk model kita, jadi tidak perlu di ubah lagi :)
Conclusion
naive <- data_frame(Accuracy = conf_naive$overall[1],
Recall = conf_naive$byClass[1],
Specificity = conf_naive$byClass[2],
Precision = conf_naive$byClass[3])
dtree <- data_frame(Accuracy = conf_tree$overall[1],
Recall = conf_tree$byClass[1],
Specificity = conf_tree$byClass[2],
Precision = conf_tree$byClass[3])
rforest <- data_frame(Accuracy = conf_forest$overall[1],
Recall = conf_forest$byClass[1],
Specificity = conf_forest$byClass[2],
Precision = conf_forest$byClass[3])rbind("Naive Bayes" = naive, "Decision Tree" = dtree, "Random Forest" = rforest)#> # A tibble: 3 × 4
#> Accuracy Recall Specificity Precision
#> * <dbl> <dbl> <dbl> <dbl>
#> 1 0.774 0.735 0.808 0.771
#> 2 0.836 0.850 0.823 0.808
#> 3 0.855 0.882 0.831 0.821
Berdasarkan hasil ke-empat metrix diatas. Saya akan memilih Random Forest karena dari segi Accuracy dan Recall yang lebih tinggi dari Decisio Tree dan Naive Bayes. Di samping itu juga karena pada kasus ini saya ingin menggunakan Metrix Recall guna mengurangin kelas False Negatif yang artinya saya ingin melakukan prediksi sebanyak-banyak nya terkait nasabah yang ingin melakukan Deposito dengan kata lain saya ingin mengurangi Nasabah yang melakukan Deposito tetapi di prediksi tidak melakuakan Deposito. Karena dengan informasi tersebut saya bisa mengekstrak kriteria dari nasabah yang melakukan Deposito lalu dapat menerapkan strategi campaign yang mampu menarik ketertarikan dari pada para nasabah di bank tempat saya bekerja.