data_train <- read.csv("C:\\Users\\Hafizh Fadhlah\\Downloads\\Data Training.csv", sep=";")
data_test <- read.csv("C:\\Users\\Hafizh Fadhlah\\Downloads\\Data Testing.csv", sep=";")
str(data_train)
## 'data.frame': 41188 obs. of 21 variables:
## $ age : int 56 57 37 40 56 45 59 41 24 25 ...
## $ job : chr "housemaid" "services" "services" "admin." ...
## $ marital : chr "married" "married" "married" "married" ...
## $ education : chr "basic.4y" "high.school" "high.school" "basic.6y" ...
## $ default : chr "no" "" "no" "no" ...
## $ housing : chr "no" "no" "yes" "no" ...
## $ loan : chr "no" "no" "no" "no" ...
## $ contact : chr "telephone" "telephone" "telephone" "telephone" ...
## $ month : chr "may" "may" "may" "may" ...
## $ day_of_week : chr "mon" "mon" "mon" "mon" ...
## $ duration : int 261 149 226 151 307 198 139 217 380 50 ...
## $ campaign : int 1 1 1 1 1 1 1 1 1 1 ...
## $ pdays : int 999 999 999 999 999 999 999 999 999 999 ...
## $ previous : int 0 0 0 0 0 0 0 0 0 0 ...
## $ poutcome : chr "nonexistent" "nonexistent" "nonexistent" "nonexistent" ...
## $ emp.var.rate : num 1.1 1.1 1.1 1.1 1.1 1.1 1.1 1.1 1.1 1.1 ...
## $ cons.price.idx: num 94 94 94 94 94 ...
## $ cons.conf.idx : num -36.4 -36.4 -36.4 -36.4 -36.4 -36.4 -36.4 -36.4 -36.4 -36.4 ...
## $ euribor3m : num 4.86 4.86 4.86 4.86 4.86 ...
## $ nr.employed : num 5191 5191 5191 5191 5191 ...
## $ y : chr "no" "no" "no" "no" ...
str(data_test)
## 'data.frame': 4119 obs. of 21 variables:
## $ age : int 30 39 25 38 47 32 32 41 31 35 ...
## $ job : chr "blue-collar" "services" "services" "services" ...
## $ marital : chr "married" "single" "married" "married" ...
## $ education : chr "basic.9y" "high.school" "high.school" "basic.9y" ...
## $ default : chr "no" "no" "no" "no" ...
## $ housing : chr "yes" "no" "yes" "" ...
## $ loan : chr "no" "no" "no" "" ...
## $ contact : chr "cellular" "telephone" "telephone" "telephone" ...
## $ month : chr "may" "may" "jun" "jun" ...
## $ day_of_week : chr "fri" "fri" "wed" "fri" ...
## $ duration : int 487 346 227 17 58 128 290 44 68 170 ...
## $ campaign : int 2 4 1 3 1 3 4 2 1 1 ...
## $ pdays : int 999 999 999 999 999 999 999 999 999 999 ...
## $ previous : int 0 0 0 0 0 2 0 0 1 0 ...
## $ poutcome : chr "nonexistent" "nonexistent" "nonexistent" "nonexistent" ...
## $ emp.var.rate : num -1.8 1.1 1.4 1.4 -0.1 -1.1 -1.1 -0.1 -0.1 1.1 ...
## $ cons.price.idx: num 92.9 94 94.5 94.5 93.2 ...
## $ cons.conf.idx : num -46.2 -36.4 -41.8 -41.8 -42 -37.5 -37.5 -42 -42 -36.4 ...
## $ euribor3m : num 1.31 4.86 4.96 4.96 4.19 ...
## $ nr.employed : num 5099 5191 5228 5228 5196 ...
## $ y : chr "no" "no" "no" "no" ...
names(data_train)
## [1] "age" "job" "marital" "education"
## [5] "default" "housing" "loan" "contact"
## [9] "month" "day_of_week" "duration" "campaign"
## [13] "pdays" "previous" "poutcome" "emp.var.rate"
## [17] "cons.price.idx" "cons.conf.idx" "euribor3m" "nr.employed"
## [21] "y"
cols_factor <- c("job","marital","education","default","housing","loan",
"contact","month","day_of_week","poutcome","y")
data_train[cols_factor] <- lapply(data_train[cols_factor], factor)
data_test[cols_factor] <- lapply(data_test[cols_factor], factor)
str(data_train)
## 'data.frame': 41188 obs. of 21 variables:
## $ age : int 56 57 37 40 56 45 59 41 24 25 ...
## $ job : Factor w/ 12 levels "","admin.","blue-collar",..: 5 9 9 2 9 9 2 3 11 9 ...
## $ marital : Factor w/ 4 levels "","divorced",..: 3 3 3 3 3 3 3 3 4 4 ...
## $ education : Factor w/ 8 levels "","basic.4y",..: 2 5 5 3 5 4 7 1 7 5 ...
## $ default : Factor w/ 3 levels "","no","yes": 2 1 2 2 2 1 2 1 2 2 ...
## $ housing : Factor w/ 3 levels "","no","yes": 2 2 3 2 2 2 2 2 3 3 ...
## $ loan : Factor w/ 3 levels "","no","yes": 2 2 2 2 3 2 2 2 2 2 ...
## $ contact : Factor w/ 2 levels "cellular","telephone": 2 2 2 2 2 2 2 2 2 2 ...
## $ month : Factor w/ 10 levels "apr","aug","dec",..: 7 7 7 7 7 7 7 7 7 7 ...
## $ day_of_week : Factor w/ 5 levels "fri","mon","thu",..: 2 2 2 2 2 2 2 2 2 2 ...
## $ duration : int 261 149 226 151 307 198 139 217 380 50 ...
## $ campaign : int 1 1 1 1 1 1 1 1 1 1 ...
## $ pdays : int 999 999 999 999 999 999 999 999 999 999 ...
## $ previous : int 0 0 0 0 0 0 0 0 0 0 ...
## $ poutcome : Factor w/ 3 levels "failure","nonexistent",..: 2 2 2 2 2 2 2 2 2 2 ...
## $ emp.var.rate : num 1.1 1.1 1.1 1.1 1.1 1.1 1.1 1.1 1.1 1.1 ...
## $ cons.price.idx: num 94 94 94 94 94 ...
## $ cons.conf.idx : num -36.4 -36.4 -36.4 -36.4 -36.4 -36.4 -36.4 -36.4 -36.4 -36.4 ...
## $ euribor3m : num 4.86 4.86 4.86 4.86 4.86 ...
## $ nr.employed : num 5191 5191 5191 5191 5191 ...
## $ y : Factor w/ 2 levels "no","yes": 1 1 1 1 1 1 1 1 1 1 ...
Tahap perbaikan tipe data dilakukan untuk memastikan bahwa peubah dalam dataset diproses sesuai dengan karakteristiknya oleh algoritma machine learning. Pada data awal, beberapa peubah kategorik seperti job, marital, education, default, housing, loan, contact, month, day_of_week, dan poutcome terbaca sebagai tipe character saat diimpor ke dalam R. Oleh karena itu, peubah-peubah tersebut perlu diubah menjadi tipe factor agar dikenali sebagai peubah kategorik oleh model klasifikasi, khususnya pada metode Classification and Regression Tree (CART). Dengan mengubah tipe data menjadi factor, algoritma dapat membagi data berdasarkan kategori yang ada secara tepat ketika membentuk pohon keputusan. Selain itu, peubah target y juga diubah menjadi factor agar model memahami bahwa permasalahan yang dianalisis merupakan klasifikasi dua kelas (yes dan no), bukan regresi.
library(skimr)
## Warning: package 'skimr' was built under R version 4.5.2
skim(data_train)
## Warning: There was 1 warning in `dplyr::summarize()`.
## ℹ In argument: `dplyr::across(tidyselect::any_of(variable_names),
## mangled_skimmers$funs)`.
## ℹ In group 0: .
## Caused by warning:
## ! There were 6 warnings in `dplyr::summarize()`.
## The first warning was:
## ℹ In argument: `dplyr::across(tidyselect::any_of(variable_names),
## mangled_skimmers$funs)`.
## Caused by warning in `sorted_count()`:
## ! Variable contains value(s) of "" that have been converted to "empty".
## ℹ Run `dplyr::last_dplyr_warnings()` to see the 5 remaining warnings.
| Name | data_train |
| Number of rows | 41188 |
| Number of columns | 21 |
| _______________________ | |
| Column type frequency: | |
| factor | 11 |
| numeric | 10 |
| ________________________ | |
| Group variables | None |
Variable type: factor
| skim_variable | n_missing | complete_rate | ordered | n_unique | top_counts |
|---|---|---|---|---|---|
| job | 0 | 1 | FALSE | 12 | adm: 10422, blu: 9254, tec: 6743, ser: 3969 |
| marital | 0 | 1 | FALSE | 4 | mar: 24928, sin: 11568, div: 4612, emp: 80 |
| education | 0 | 1 | FALSE | 8 | uni: 12168, hig: 9515, bas: 6045, pro: 5243 |
| default | 0 | 1 | FALSE | 3 | no: 32588, emp: 8597, yes: 3 |
| housing | 0 | 1 | FALSE | 3 | yes: 21576, no: 18622, emp: 990 |
| loan | 0 | 1 | FALSE | 3 | no: 33950, yes: 6248, emp: 990 |
| contact | 0 | 1 | FALSE | 2 | cel: 26144, tel: 15044 |
| month | 0 | 1 | FALSE | 10 | may: 13769, jul: 7174, aug: 6178, jun: 5318 |
| day_of_week | 0 | 1 | FALSE | 5 | thu: 8623, mon: 8514, wed: 8134, tue: 8090 |
| poutcome | 0 | 1 | FALSE | 3 | non: 35563, fai: 4252, suc: 1373 |
| y | 0 | 1 | FALSE | 2 | no: 36548, yes: 4640 |
Variable type: numeric
| skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|
| age | 0 | 1 | 40.02 | 10.42 | 17.00 | 32.00 | 38.00 | 47.00 | 98.00 | ▅▇▃▁▁ |
| duration | 0 | 1 | 258.29 | 259.28 | 0.00 | 102.00 | 180.00 | 319.00 | 4918.00 | ▇▁▁▁▁ |
| campaign | 0 | 1 | 2.57 | 2.77 | 1.00 | 1.00 | 2.00 | 3.00 | 56.00 | ▇▁▁▁▁ |
| pdays | 0 | 1 | 962.48 | 186.91 | 0.00 | 999.00 | 999.00 | 999.00 | 999.00 | ▁▁▁▁▇ |
| previous | 0 | 1 | 0.17 | 0.49 | 0.00 | 0.00 | 0.00 | 0.00 | 7.00 | ▇▁▁▁▁ |
| emp.var.rate | 0 | 1 | 0.08 | 1.57 | -3.40 | -1.80 | 1.10 | 1.40 | 1.40 | ▁▃▁▁▇ |
| cons.price.idx | 0 | 1 | 93.58 | 0.58 | 92.20 | 93.08 | 93.75 | 93.99 | 94.77 | ▁▆▃▇▂ |
| cons.conf.idx | 0 | 1 | -40.50 | 4.63 | -50.80 | -42.70 | -41.80 | -36.40 | -26.90 | ▅▇▁▇▁ |
| euribor3m | 0 | 1 | 3.62 | 1.73 | 0.63 | 1.34 | 4.86 | 4.96 | 5.04 | ▅▁▁▁▇ |
| nr.employed | 0 | 1 | 5167.04 | 72.25 | 4963.60 | 5099.10 | 5191.00 | 5228.10 | 5228.10 | ▁▁▃▁▇ |
Pada data training, tidak ditemukan adanya missing value pada seluruh peubah, baik numerik maupun kategorik. Hal ini menunjukkan bahwa dataset sudah bersih dari nilai yang hilang, sehingga tidak memerlukan langkah tambahan untuk penanganan missing value seperti imputasi atau penghapusan observasi.
summary(data_train)
## age job marital
## Min. :17.00 admin. :10422 : 80
## 1st Qu.:32.00 blue-collar: 9254 divorced: 4612
## Median :38.00 technician : 6743 married :24928
## Mean :40.02 services : 3969 single :11568
## 3rd Qu.:47.00 management : 2924
## Max. :98.00 retired : 1720
## (Other) : 6156
## education default housing loan
## university.degree :12168 : 8597 : 990 : 990
## high.school : 9515 no :32588 no :18622 no :33950
## basic.9y : 6045 yes: 3 yes:21576 yes: 6248
## professional.course: 5243
## basic.4y : 4176
## basic.6y : 2292
## (Other) : 1749
## contact month day_of_week duration
## cellular :26144 may :13769 fri:7827 Min. : 0.0
## telephone:15044 jul : 7174 mon:8514 1st Qu.: 102.0
## aug : 6178 thu:8623 Median : 180.0
## jun : 5318 tue:8090 Mean : 258.3
## nov : 4101 wed:8134 3rd Qu.: 319.0
## apr : 2632 Max. :4918.0
## (Other): 2016
## campaign pdays previous poutcome
## Min. : 1.000 Min. : 0.0 Min. :0.000 failure : 4252
## 1st Qu.: 1.000 1st Qu.:999.0 1st Qu.:0.000 nonexistent:35563
## Median : 2.000 Median :999.0 Median :0.000 success : 1373
## Mean : 2.568 Mean :962.5 Mean :0.173
## 3rd Qu.: 3.000 3rd Qu.:999.0 3rd Qu.:0.000
## Max. :56.000 Max. :999.0 Max. :7.000
##
## emp.var.rate cons.price.idx cons.conf.idx euribor3m
## Min. :-3.40000 Min. :92.20 Min. :-50.8 Min. :0.634
## 1st Qu.:-1.80000 1st Qu.:93.08 1st Qu.:-42.7 1st Qu.:1.344
## Median : 1.10000 Median :93.75 Median :-41.8 Median :4.857
## Mean : 0.08189 Mean :93.58 Mean :-40.5 Mean :3.621
## 3rd Qu.: 1.40000 3rd Qu.:93.99 3rd Qu.:-36.4 3rd Qu.:4.961
## Max. : 1.40000 Max. :94.77 Max. :-26.9 Max. :5.045
##
## nr.employed y
## Min. :4964 no :36548
## 1st Qu.:5099 yes: 4640
## Median :5191
## Mean :5167
## 3rd Qu.:5228
## Max. :5228
##
table(data_train$y)
##
## no yes
## 36548 4640
prop.table(table(data_train$y))
##
## no yes
## 0.8873458 0.1126542
Distribusi peubah target y menunjukkan bahwa sebagian besar nasabah dalam data training tidak berlangganan deposito berjangka (no). Dari total 41.188 observasi, sebanyak 36.548 nasabah (88,73%) termasuk dalam kategori no, sedangkan hanya 4.640 nasabah (11,27%) yang termasuk dalam kategori yes. Hal ini menunjukkan bahwa dataset memiliki ketidakseimbangan kelas (class imbalance).
library(gmodels)
## Warning: package 'gmodels' was built under R version 4.5.2
CrossTable(data_train$job, data_train$y,
prop.chisq = FALSE,
chisq = FALSE,
fisher = FALSE,
format = "SPSS")
##
## Cell Contents
## |-------------------------|
## | Count |
## | Row Percent |
## | Column Percent |
## | Total Percent |
## |-------------------------|
##
## Total Observations in Table: 41188
##
## | data_train$y
## data_train$job | no | yes | Row Total |
## ---------------|-----------|-----------|-----------|
## | 293 | 37 | 330 |
## | 88.788% | 11.212% | 0.801% |
## | 0.802% | 0.797% | |
## | 0.711% | 0.090% | |
## ---------------|-----------|-----------|-----------|
## admin. | 9070 | 1352 | 10422 |
## | 87.027% | 12.973% | 25.303% |
## | 24.817% | 29.138% | |
## | 22.021% | 3.283% | |
## ---------------|-----------|-----------|-----------|
## blue-collar | 8616 | 638 | 9254 |
## | 93.106% | 6.894% | 22.468% |
## | 23.574% | 13.750% | |
## | 20.919% | 1.549% | |
## ---------------|-----------|-----------|-----------|
## entrepreneur | 1332 | 124 | 1456 |
## | 91.484% | 8.516% | 3.535% |
## | 3.645% | 2.672% | |
## | 3.234% | 0.301% | |
## ---------------|-----------|-----------|-----------|
## housemaid | 954 | 106 | 1060 |
## | 90.000% | 10.000% | 2.574% |
## | 2.610% | 2.284% | |
## | 2.316% | 0.257% | |
## ---------------|-----------|-----------|-----------|
## management | 2596 | 328 | 2924 |
## | 88.782% | 11.218% | 7.099% |
## | 7.103% | 7.069% | |
## | 6.303% | 0.796% | |
## ---------------|-----------|-----------|-----------|
## retired | 1286 | 434 | 1720 |
## | 74.767% | 25.233% | 4.176% |
## | 3.519% | 9.353% | |
## | 3.122% | 1.054% | |
## ---------------|-----------|-----------|-----------|
## self-employed | 1272 | 149 | 1421 |
## | 89.514% | 10.486% | 3.450% |
## | 3.480% | 3.211% | |
## | 3.088% | 0.362% | |
## ---------------|-----------|-----------|-----------|
## services | 3646 | 323 | 3969 |
## | 91.862% | 8.138% | 9.636% |
## | 9.976% | 6.961% | |
## | 8.852% | 0.784% | |
## ---------------|-----------|-----------|-----------|
## student | 600 | 275 | 875 |
## | 68.571% | 31.429% | 2.124% |
## | 1.642% | 5.927% | |
## | 1.457% | 0.668% | |
## ---------------|-----------|-----------|-----------|
## technician | 6013 | 730 | 6743 |
## | 89.174% | 10.826% | 16.371% |
## | 16.452% | 15.733% | |
## | 14.599% | 1.772% | |
## ---------------|-----------|-----------|-----------|
## unemployed | 870 | 144 | 1014 |
## | 85.799% | 14.201% | 2.462% |
## | 2.380% | 3.103% | |
## | 2.112% | 0.350% | |
## ---------------|-----------|-----------|-----------|
## Column Total | 36548 | 4640 | 41188 |
## | 88.735% | 11.265% | |
## ---------------|-----------|-----------|-----------|
##
##
CrossTable(data_train$marital, data_train$y,
prop.chisq = FALSE,
chisq = FALSE,
fisher = FALSE,
format = "SPSS")
##
## Cell Contents
## |-------------------------|
## | Count |
## | Row Percent |
## | Column Percent |
## | Total Percent |
## |-------------------------|
##
## Total Observations in Table: 41188
##
## | data_train$y
## data_train$marital | no | yes | Row Total |
## -------------------|-----------|-----------|-----------|
## | 68 | 12 | 80 |
## | 85.000% | 15.000% | 0.194% |
## | 0.186% | 0.259% | |
## | 0.165% | 0.029% | |
## -------------------|-----------|-----------|-----------|
## divorced | 4136 | 476 | 4612 |
## | 89.679% | 10.321% | 11.197% |
## | 11.317% | 10.259% | |
## | 10.042% | 1.156% | |
## -------------------|-----------|-----------|-----------|
## married | 22396 | 2532 | 24928 |
## | 89.843% | 10.157% | 60.522% |
## | 61.278% | 54.569% | |
## | 54.375% | 6.147% | |
## -------------------|-----------|-----------|-----------|
## single | 9948 | 1620 | 11568 |
## | 85.996% | 14.004% | 28.086% |
## | 27.219% | 34.914% | |
## | 24.153% | 3.933% | |
## -------------------|-----------|-----------|-----------|
## Column Total | 36548 | 4640 | 41188 |
## | 88.735% | 11.265% | |
## -------------------|-----------|-----------|-----------|
##
##
CrossTable(data_train$education, data_train$y,
prop.chisq = FALSE,
chisq = FALSE,
fisher = FALSE,
format = "SPSS")
##
## Cell Contents
## |-------------------------|
## | Count |
## | Row Percent |
## | Column Percent |
## | Total Percent |
## |-------------------------|
##
## Total Observations in Table: 41188
##
## | data_train$y
## data_train$education | no | yes | Row Total |
## ---------------------|-----------|-----------|-----------|
## | 1480 | 251 | 1731 |
## | 85.500% | 14.500% | 4.203% |
## | 4.049% | 5.409% | |
## | 3.593% | 0.609% | |
## ---------------------|-----------|-----------|-----------|
## basic.4y | 3748 | 428 | 4176 |
## | 89.751% | 10.249% | 10.139% |
## | 10.255% | 9.224% | |
## | 9.100% | 1.039% | |
## ---------------------|-----------|-----------|-----------|
## basic.6y | 2104 | 188 | 2292 |
## | 91.798% | 8.202% | 5.565% |
## | 5.757% | 4.052% | |
## | 5.108% | 0.456% | |
## ---------------------|-----------|-----------|-----------|
## basic.9y | 5572 | 473 | 6045 |
## | 92.175% | 7.825% | 14.677% |
## | 15.246% | 10.194% | |
## | 13.528% | 1.148% | |
## ---------------------|-----------|-----------|-----------|
## high.school | 8484 | 1031 | 9515 |
## | 89.164% | 10.836% | 23.101% |
## | 23.213% | 22.220% | |
## | 20.598% | 2.503% | |
## ---------------------|-----------|-----------|-----------|
## illiterate | 14 | 4 | 18 |
## | 77.778% | 22.222% | 0.044% |
## | 0.038% | 0.086% | |
## | 0.034% | 0.010% | |
## ---------------------|-----------|-----------|-----------|
## professional.course | 4648 | 595 | 5243 |
## | 88.652% | 11.348% | 12.729% |
## | 12.718% | 12.823% | |
## | 11.285% | 1.445% | |
## ---------------------|-----------|-----------|-----------|
## university.degree | 10498 | 1670 | 12168 |
## | 86.275% | 13.725% | 29.543% |
## | 28.724% | 35.991% | |
## | 25.488% | 4.055% | |
## ---------------------|-----------|-----------|-----------|
## Column Total | 36548 | 4640 | 41188 |
## | 88.735% | 11.265% | |
## ---------------------|-----------|-----------|-----------|
##
##
CrossTable(data_train$housing, data_train$y,
prop.chisq = FALSE,
chisq = FALSE,
fisher = FALSE,
format = "SPSS")
##
## Cell Contents
## |-------------------------|
## | Count |
## | Row Percent |
## | Column Percent |
## | Total Percent |
## |-------------------------|
##
## Total Observations in Table: 41188
##
## | data_train$y
## data_train$housing | no | yes | Row Total |
## -------------------|-----------|-----------|-----------|
## | 883 | 107 | 990 |
## | 89.192% | 10.808% | 2.404% |
## | 2.416% | 2.306% | |
## | 2.144% | 0.260% | |
## -------------------|-----------|-----------|-----------|
## no | 16596 | 2026 | 18622 |
## | 89.120% | 10.880% | 45.212% |
## | 45.409% | 43.664% | |
## | 40.293% | 4.919% | |
## -------------------|-----------|-----------|-----------|
## yes | 19069 | 2507 | 21576 |
## | 88.381% | 11.619% | 52.384% |
## | 52.175% | 54.030% | |
## | 46.297% | 6.087% | |
## -------------------|-----------|-----------|-----------|
## Column Total | 36548 | 4640 | 41188 |
## | 88.735% | 11.265% | |
## -------------------|-----------|-----------|-----------|
##
##
Dengan melihat tabel kontingensi tersebut, kita dapat mengidentifikasi bahwa terdapat beberapa peubah yang memiliki hubungan yang kuat dengan peubah target y. Misalnya, pada peubah job, terlihat bahwa nasabah dengan pekerjaan tertentu seperti “admin.” dan “blue-collar” memiliki proporsi yang lebih tinggi untuk tidak berlangganan deposito (no). Informasi ini dapat digunakan sebagai dasar dalam pemilihan peubah untuk model klasifikasi dan dalam memahami karakteristik nasabah yang lebih mungkin untuk berlangganan deposito.
entropy <- function(counts) {
counts <- as.numeric(counts)
p <- counts / sum(counts)
p <- p[p > 0]
-sum(p * log2(p))
}
jumlah_y <- table(data_train$y)
entropy_total <- entropy(jumlah_y)
entropy_total
## [1] 0.5078702
Berdasarkan perhitungan entropy total pada peubah target y, diperoleh nilai sebesar 0,5079. Nilai ini mencerminkan tingkat ketidakmurnian (impurity) keseluruhan data latih, di mana semakin tinggi nilai entropy maka semakin beragam distribusi kelasnya. Nilai 0,5079 yang lebih rendah dari nilai maksimum entropy yaitu 1, mengindikasikan bahwa data tidak sepenuhnya seimbang antara kelas “no” dan “yes”, yang sejalan dengan temuan sebelumnya bahwa terdapat ketidakseimbangan kelas dengan proporsi 88,73% berbanding 11,27%.
data_train <- subset(data_train, select = -duration)
data_test <- subset(data_test, select = -duration)
library(rpart)
## Warning: package 'rpart' was built under R version 4.5.2
library(rpart.plot)
## Warning: package 'rpart.plot' was built under R version 4.5.2
model_tree <- rpart(
y ~ .,
data = data_train,
method = "class",
control = rpart.control(cp = 0.01,
minsplit = 20,
maxdepth = 5)
)
printcp(model_tree)
##
## Classification tree:
## rpart(formula = y ~ ., data = data_train, method = "class", control = rpart.control(cp = 0.01,
## minsplit = 20, maxdepth = 5))
##
## Variables actually used in tree construction:
## [1] nr.employed pdays
##
## Root node error: 4640/41188 = 0.11265
##
## n= 41188
##
## CP nsplit rel error xerror xstd
## 1 0.053879 0 1.00000 1.0000 0.013829
## 2 0.010000 2 0.89224 0.8972 0.013184
Pemodelan Classification Tree tanpa peubah duration menunjukkan bahwa hanya 2 peubah yang digunakan dalam konstruksi pohon, yaitu nr.employed dan pdays. Tabel CP menunjukkan bahwa model hanya melakukan 2 split dengan nilai relative error yang menurun dari 1,000 menjadi 0,892 dan cross-validation error sebesar 0,895, yang mengindikasikan model yang sangat sederhana namun berpotensi kurang mampu menangkap pola data secara optimal akibat keterbatasan peubah prediktor yang tersedia setelah duration disisihkan.
rpart.plot(
model_tree,
type = 2,
extra = 104,
fallen.leaves = TRUE
)
Visualisasi classification tree tanpa peubah duration menunjukkan struktur yang sangat sederhana dengan hanya 3 leaf node dan menggunakan 2 peubah pemisah yaitu nr.employed dan pdays. Pada root node, seluruh 100% data menunjukkan proporsi 89% “no” dan 11% “yes” yang mencerminkan kondisi ketidakseimbangan kelas. Pemisahan pertama dilakukan berdasarkan nr.employed ≥ 5088, di mana nasabah dengan nr.employed ≥ 5088 yang mencakup 88% data diprediksi dominan sebagai “no” dengan proporsi 93% berbanding 7%, mengindikasikan bahwa kondisi ketenagakerjaan yang tinggi berkaitan erat dengan keengganan nasabah berlangganan deposito. Pada cabang kanan dengan nr.employed < 5088 (12% data), pemisahan lanjutan menggunakan pdays ≥ 17 menghasilkan dua node akhir, di mana nasabah dengan pdays < 17 (dihubungi kurang dari 17 hari sejak promosi sebelumnya) menghasilkan proporsi 71% “yes” meskipun hanya mencakup 3% data, sementara nasabah dengan pdays ≥ 17 menunjukkan proporsi 64% “no” berbanding 36% “yes”. Secara keseluruhan, pohon ini mengidentifikasi nr.employed dan pdays sebagai faktor kunci dalam menentukan keputusan nasabah.
library(caret)
## Warning: package 'caret' was built under R version 4.5.2
## Loading required package: ggplot2
## Loading required package: lattice
pred_train_nodur <- predict(model_tree,
newdata = data_train,
type = "class")
data_train$y <- factor(data_train$y,
levels = levels(data_train$y))
cm_train <- confusionMatrix(data = pred_train_nodur,
reference = data_train$y,
positive = "yes")
cm_train
## Confusion Matrix and Statistics
##
## Reference
## Prediction no yes
## no 36189 3781
## yes 359 859
##
## Accuracy : 0.8995
## 95% CI : (0.8965, 0.9024)
## No Information Rate : 0.8873
## P-Value [Acc > NIR] : 1.282e-15
##
## Kappa : 0.2585
##
## Mcnemar's Test P-Value : < 2.2e-16
##
## Sensitivity : 0.18513
## Specificity : 0.99018
## Pos Pred Value : 0.70525
## Neg Pred Value : 0.90540
## Prevalence : 0.11265
## Detection Rate : 0.02086
## Detection Prevalence : 0.02957
## Balanced Accuracy : 0.58765
##
## 'Positive' Class : yes
##
precision_train <- cm_train$byClass["Pos Pred Value"]
recall_train <- cm_train$byClass["Sensitivity"]
F1_train <- 2 * (precision_train * recall_train) /
(precision_train + recall_train)
F1_train
## Pos Pred Value
## 0.2932742
pred_class <- predict(model_tree,
newdata = data_test,
type = "class")
library(caret)
data_test$y <- factor(data_test$y,
levels = levels(data_train$y))
cm <- confusionMatrix(data = pred_class,
reference = data_test$y,
positive = "yes")
cm
## Confusion Matrix and Statistics
##
## Reference
## Prediction no yes
## no 3627 362
## yes 41 89
##
## Accuracy : 0.9022
## 95% CI : (0.8927, 0.9111)
## No Information Rate : 0.8905
## P-Value [Acc > NIR] : 0.008157
##
## Kappa : 0.2706
##
## Mcnemar's Test P-Value : < 2.2e-16
##
## Sensitivity : 0.19734
## Specificity : 0.98882
## Pos Pred Value : 0.68462
## Neg Pred Value : 0.90925
## Prevalence : 0.10949
## Detection Rate : 0.02161
## Detection Prevalence : 0.03156
## Balanced Accuracy : 0.59308
##
## 'Positive' Class : yes
##
precision <- cm$byClass["Pos Pred Value"]
recall <- cm$byClass["Sensitivity"]
F1 <- 2 * (precision * recall) / (precision + recall)
F1
## Pos Pred Value
## 0.3063683
library(pROC)
## Warning: package 'pROC' was built under R version 4.5.2
## Type 'citation("pROC")' for a citation.
##
## Attaching package: 'pROC'
## The following object is masked from 'package:gmodels':
##
## ci
## The following objects are masked from 'package:stats':
##
## cov, smooth, var
pred_prob <- predict(model_tree,
newdata = data_test,
type = "prob")[,"yes"]
roc_obj <- roc(response = data_test$y,
predictor = pred_prob,
levels = c("no","yes"),
direction = "<")
plot(roc_obj,
legacy.axes = TRUE,
xlab = "1 - Specificity (False Positive Rate)",
ylab = "Sensitivity (True Positive Rate)")
auc(roc_obj)
## Area under the curve: 0.7157
Berdasarkan hasil evaluasi model Classification Tree tanpa peubah duration pada data testing, model mencapai akurasi sebesar 90,22%, namun performa model jauh lebih lemah jika dilihat dari kemampuan mendeteksi kelas positif. Sensitivity hanya 19,73% menunjukkan bahwa model hanya mampu mendeteksi 89 dari 451 nasabah yang benar-benar berlangganan deposito, sementara 362 nasabah “yes” lainnya salah diprediksi sebagai “no” (false negative). Specificity sangat tinggi sebesar 98,88% mengindikasikan model sangat dominan memprediksi kelas mayoritas “no”, yang merupakan dampak langsung dari ketidakseimbangan kelas yang belum ditangani. Balanced Accuracy hanya 59,31% mencerminkan performa yang hampir setara dengan tebakan acak, dan F1 Score sebesar 0,3064 mengkonfirmasi ketidakseimbangan yang besar antara Precision dan Recall. Kurva ROC terlihat kurang optimal dengan lekukan yang tidak tajam ke sudut kiri atas, dan AUC sebesar 0,7157 yang semakin mengkonfirmasi bahwa perlu penanganan ketidakseimbangan kelas melalui metode SMOTE.
library(themis)
## Warning: package 'themis' was built under R version 4.5.2
## Loading required package: recipes
## Warning: package 'recipes' was built under R version 4.5.2
## Loading required package: dplyr
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
##
## Attaching package: 'recipes'
## The following object is masked from 'package:stats':
##
## step
library(recipes)
recipe_smote <- recipe(y ~ ., data = data_train) %>%
step_smotenc(y, over_ratio = 1, seed = 123)
recipe_trained <- prep(recipe_smote, training = data_train)
data_train_smote <- bake(recipe_trained, new_data = NULL)
table(data_train_smote$y)
##
## no yes
## 36548 36548
prop.table(table(data_train_smote$y))
##
## no yes
## 0.5 0.5
Setelah diterapkan teknik SMOTE, distribusi kelas pada data latih berhasil diseimbangkan secara sempurna menjadi 50:50, dengan masing-masing kelas “no” dan “yes” memiliki 36.548 observasi. Hal ini menunjukkan bahwa SMOTE berhasil membangkitkan data sintetis pada kelas minoritas “yes” yang sebelumnya hanya berjumlah 4.640 menjadi 36.548, sehingga jumlahnya kini setara dengan kelas mayoritas “no”
model_tree_smote <- rpart(
y ~ .,
data = data_train_smote,
method = "class",
control = rpart.control(cp = 0.01,
minsplit = 20,
maxdepth = 5)
)
printcp(model_tree_smote)
##
## Classification tree:
## rpart(formula = y ~ ., data = data_train_smote, method = "class",
## control = rpart.control(cp = 0.01, minsplit = 20, maxdepth = 5))
##
## Variables actually used in tree construction:
## [1] campaign contact month nr.employed
##
## Root node error: 36548/73096 = 0.5
##
## n= 73096
##
## CP nsplit rel error xerror xstd
## 1 0.402922 0 1.00000 1.00813 0.0036986
## 2 0.067418 1 0.59708 0.59708 0.0033852
## 3 0.021998 2 0.52966 0.52966 0.0032641
## 4 0.012764 5 0.46366 0.46369 0.0031218
## 5 0.010000 7 0.43814 0.44842 0.0030852
Hasil pemodelan Classification Tree menggunakan data SMOTE menunjukkan bahwa terdapat 4 peubah yang digunakan dalam konstruksi pohon, yaitu campaign, contact, month, dan nr.employed. Hasil ini berbeda signifikan dibandingkan model awal tanpa duration yang hanya menggunakan nr.employed dan pdays, mengindikasikan bahwa setelah data diseimbangkan melalui SMOTE, model mampu mengidentifikasi pola yang lebih beragam dari data sehingga peubah seperti campaign, contact, dan month kini turut berkontribusi dalam proses klasifikasi. Root node error sebesar 0,5 mencerminkan kondisi data latih yang sudah seimbang sempurna (50:50), dan seiring bertambahnya jumlah split dari 0 hingga 7, nilai relative error menurun drastis dari 1,000 menjadi 0,438 serta cross-validation error menurun hingga 0,446, yang menunjukkan bahwa model SMOTE memiliki kemampuan belajar yang jauh lebih baik dibandingkan model awal dengan jumlah split yang lebih banyak (7 split vs 2 split), mengindikasikan pohon yang lebih kompleks dan mampu menangkap pola data secara lebih mendalam.
rpart.plot(
model_tree_smote,
type = 2,
extra = 104,
fallen.leaves = TRUE
)
Visualisasi classification tree model SMOTE tanpa peubah duration menunjukkan struktur yang jauh lebih kompleks dibandingkan model awal, dengan menggunakan 4 peubah pemisah yaitu nr.employed, month, contact, dan campaign. Pada root node, proporsi kelas “no” dan “yes” kini seimbang 50:50 mencerminkan keberhasilan SMOTE dalam menyeimbangkan data latih. Pemisahan pertama dilakukan berdasarkan nr.employed ≥ 5099, di mana nasabah dengan nr.employed ≥ 5099 (28% data, cabang kanan) langsung diprediksi “yes” dengan proporsi 36% berbanding 64%, sementara cabang kiri dengan nr.employed < 5099 (72% data) dilanjutkan pemisahan menggunakan month. peubah month membagi data berdasarkan bulan kontak, di mana bulan aug, dec, jul, jun, may, nov (63% data) cenderung diprediksi “no” dengan proporsi 69% berbanding 31%, sedangkan bulan lainnya menunjukkan peluang “yes” yang lebih tinggi. Pemisahan lebih lanjut menggunakan contact dan campaign semakin memperhalus klasifikasi, dengan node terhijau paling kuat mencapai proporsi 100% “yes” pada nasabah yang dihubungi hanya 1 kali (campaign < 1 tidak terpenuhi) pada kelompok bulan tertentu meskipun hanya mencakup proporsi kecil data. Secara keseluruhan, SMOTE jauh lebih informatif dibandingkan model awal karena mampu mengidentifikasi kombinasi faktor yang lebih beragam dalam menentukan keputusan nasabah berlangganan deposito.
pred_train_smote <- predict(model_tree_smote,
newdata = data_train_smote,
type = "class")
data_train_smote$y <- factor(data_train_smote$y,
levels = levels(data_train_smote$y))
cm_train_smote <- confusionMatrix(data = pred_train_smote,
reference = data_train_smote$y,
positive = "yes")
cm_train_smote
## Confusion Matrix and Statistics
##
## Reference
## Prediction no yes
## no 31596 11061
## yes 4952 25487
##
## Accuracy : 0.7809
## 95% CI : (0.7779, 0.7839)
## No Information Rate : 0.5
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.5619
##
## Mcnemar's Test P-Value : < 2.2e-16
##
## Sensitivity : 0.6974
## Specificity : 0.8645
## Pos Pred Value : 0.8373
## Neg Pred Value : 0.7407
## Prevalence : 0.5000
## Detection Rate : 0.3487
## Detection Prevalence : 0.4164
## Balanced Accuracy : 0.7809
##
## 'Positive' Class : yes
##
precision_train_smote <- cm_train_smote$byClass["Pos Pred Value"]
recall_train_smote <- cm_train_smote$byClass["Sensitivity"]
F1_train_smote <- 2 * (precision_train_smote * recall_train_smote) /
(precision_train_smote + recall_train_smote)
F1_train_smote
## Pos Pred Value
## 0.7609536
pred_smote <- predict(model_tree_smote,
newdata = data_test,
type = "class")
cm_smote <- confusionMatrix(data = pred_smote,
reference = data_test$y,
positive = "yes")
cm_smote
## Confusion Matrix and Statistics
##
## Reference
## Prediction no yes
## no 3186 182
## yes 482 269
##
## Accuracy : 0.8388
## 95% CI : (0.8272, 0.8499)
## No Information Rate : 0.8905
## P-Value [Acc > NIR] : 1
##
## Kappa : 0.36
##
## Mcnemar's Test P-Value : <2e-16
##
## Sensitivity : 0.59645
## Specificity : 0.86859
## Pos Pred Value : 0.35819
## Neg Pred Value : 0.94596
## Prevalence : 0.10949
## Detection Rate : 0.06531
## Detection Prevalence : 0.18233
## Balanced Accuracy : 0.73252
##
## 'Positive' Class : yes
##
precision_smote <- cm_smote$byClass["Pos Pred Value"]
recall_smote <- cm_smote$byClass["Sensitivity"]
F1_smote <- 2 * (precision_smote * recall_smote) /
(precision_smote + recall_smote)
F1_smote
## Pos Pred Value
## 0.4475874
pred_prob_smote <- predict(model_tree_smote,
newdata = data_test,
type = "prob")[,"yes"]
roc_obj_smote <- roc(response = data_test$y,
predictor = pred_prob_smote,
levels = c("no", "yes"),
direction = "<")
plot(roc_obj_smote,
legacy.axes = TRUE,
xlab = "1 - Specificity (False Positive Rate)",
ylab = "Sensitivity (True Positive Rate)")
auc(roc_obj_smote)
## Area under the curve: 0.7572
Berdasarkan hasil evaluasi model SMOTE tanpa peubah duration pada data testing, terjadi peningkatan performa yang signifikan dibandingkan model awal. Meskipun akurasi menurun menjadi 83,88%, penurunan ini diimbangi dengan peningkatan drastis pada kemampuan deteksi kelas positif, di mana Sensitivity meningkat dari 19,73% menjadi 59,65%, artinya model kini berhasil mendeteksi 269 dari 451 nasabah yang benar-benar berlangganan deposito 3 kali lebih baik dibandingkan model awal. Balanced Accuracy meningkat dari 59,31% menjadi 73,25% yang merupakan ukuran performa lebih representatif, F1 Score meningkat dari 0,3064 menjadi 0,4476. Kurva ROC model SMOTE terlihat lebih melebar ke sudut kiri atas dibandingkan model awal, dengan AUC meningkat dari 0,7157 menjadi 0,7572 yang menunjukkan peningkatan kemampuan diskriminasi model. Secara keseluruhan, penerapan SMOTE terbukti berhasil mengatasi dampak ketidakseimbangan kelas sehingga model menjadi lebih akurat dalam mengidentifikasi nasabah potensial yang berlangganan deposito, meskipun harus mengorbankan sedikit akurasi dan Specificity.
hasil_perbandingan <- data.frame(
Model = c("Model awal tanpa Duration (Train)",
"Model awal tanpa Duration (Test)",
"Model SMOTE tanpa Duration (Train)",
"Model SMOTE tanpa Duration (Test)"),
Accuracy = round(c(
cm_train$overall["Accuracy"],
cm$overall["Accuracy"],
cm_train_smote$overall["Accuracy"],
cm_smote$overall["Accuracy"]), 4),
Sensitivity = round(c(
cm_train$byClass["Sensitivity"],
cm$byClass["Sensitivity"],
cm_train_smote$byClass["Sensitivity"],
cm_smote$byClass["Sensitivity"]), 4),
Specificity = round(c(
cm_train$byClass["Specificity"],
cm$byClass["Specificity"],
cm_train_smote$byClass["Specificity"],
cm_smote$byClass["Specificity"]), 4),
Balanced_Accuracy = round(c(
cm_train$byClass["Balanced Accuracy"],
cm$byClass["Balanced Accuracy"],
cm_train_smote$byClass["Balanced Accuracy"],
cm_smote$byClass["Balanced Accuracy"]), 4),
F1_Score = round(c(
F1_train,
F1,
F1_train_smote,
F1_smote), 4)
)
print(hasil_perbandingan)
## Model Accuracy Sensitivity Specificity
## 1 Model awal tanpa Duration (Train) 0.8995 0.1851 0.9902
## 2 Model awal tanpa Duration (Test) 0.9022 0.1973 0.9888
## 3 Model SMOTE tanpa Duration (Train) 0.7809 0.6974 0.8645
## 4 Model SMOTE tanpa Duration (Test) 0.8388 0.5965 0.8686
## Balanced_Accuracy F1_Score
## 1 0.5877 0.2933
## 2 0.5931 0.3064
## 3 0.7809 0.7610
## 4 0.7325 0.4476
Berdasarkan tabel perbandingan performa keempat model, terdapat perbedaan yang signifikan antara model awal dan model SMOTE dalam mendeteksi nasabah yang berlangganan deposito. Model awal baik pada data training maupun testing menunjukkan akurasi yang tinggi (89,95% dan 90,22%), namun performa sesungguhnya sangat lemah dengan Sensitivity hanya 18,51% dan 19,73%, artinya model hampir gagal total dalam mendeteksi nasabah “yes” akibat dampak ketidakseimbangan kelas yang tidak ditangani. Nilai Balanced Accuracy yang hanya 58,77% dan 59,31% pada model awal mengkonfirmasi bahwa performa model hampir setara dengan tebakan acak untuk kelas minoritas.
Sebaliknya, Model SMOTE menunjukkan peningkatan yang signifikan pada kemampuan deteksi kelas positif, di mana Sensitivity meningkat drastis menjadi 69,74% pada data training dan 59,65% pada data testing, yang berarti model kini mampu mendeteksi lebih dari separuh nasabah yang berpotensi berlangganan deposito. Balanced Accuracy meningkat menjadi 78,09% dan 73,25% yang jauh lebih representatif. Penurunan performa dari training ke testing pada model SMOTE (Accuracy 78,09% → 83,88%, Sensitivity 69,74% → 59,65%) mengindikasikan adanya sedikit overfitting namun masih dalam batas yang wajar. Secara keseluruhan, model SMOTE tanpa duration pada data testing merupakan model terbaik untuk konteks bisnis perbankan ini karena lebih realistis, tidak menggunakan peubah yang baru diketahui setelah panggilan selesai, dan memiliki kemampuan deteksi nasabah potensial yang jauh lebih baik dibandingkan model awal.
printcp(model_tree_smote)
##
## Classification tree:
## rpart(formula = y ~ ., data = data_train_smote, method = "class",
## control = rpart.control(cp = 0.01, minsplit = 20, maxdepth = 5))
##
## Variables actually used in tree construction:
## [1] campaign contact month nr.employed
##
## Root node error: 36548/73096 = 0.5
##
## n= 73096
##
## CP nsplit rel error xerror xstd
## 1 0.402922 0 1.00000 1.00813 0.0036986
## 2 0.067418 1 0.59708 0.59708 0.0033852
## 3 0.021998 2 0.52966 0.52966 0.0032641
## 4 0.012764 5 0.46366 0.46369 0.0031218
## 5 0.010000 7 0.43814 0.44842 0.0030852
plotcp(model_tree_smote)
cp_optimal <- model_tree_smote$cptable[
which.min(model_tree_smote$cptable[,"xerror"]), "CP"]
model_tree_smote_pruned <- prune(model_tree_smote, cp = cp_optimal)
pred_pruned <- predict(model_tree_smote_pruned,
newdata = data_test,
type = "class")
confusionMatrix(pred_pruned, data_test$y, positive = "yes")
## Confusion Matrix and Statistics
##
## Reference
## Prediction no yes
## no 3186 182
## yes 482 269
##
## Accuracy : 0.8388
## 95% CI : (0.8272, 0.8499)
## No Information Rate : 0.8905
## P-Value [Acc > NIR] : 1
##
## Kappa : 0.36
##
## Mcnemar's Test P-Value : <2e-16
##
## Sensitivity : 0.59645
## Specificity : 0.86859
## Pos Pred Value : 0.35819
## Neg Pred Value : 0.94596
## Prevalence : 0.10949
## Detection Rate : 0.06531
## Detection Prevalence : 0.18233
## Balanced Accuracy : 0.73252
##
## 'Positive' Class : yes
##
Terdapat sedikit indikasi overfitting pada model SMOTE yang terlihat dari penurunan Sensitivity dari 69,74% pada data training menjadi 59,65% pada data testing. Namun berdasarkan hasil plot cp, nilai CP optimal sudah tercapai pada cp = 0.010 sehingga pruning tidak diperlukan. Perbedaan performa ini kemungkinan disebabkan oleh perbedaan distribusi data sintetis SMOTE pada data training dengan data testing yang merupakan data asli.