1. Persiapan Data

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"

2. Perbaikan Tipe Data (Mengubah beberapa peubah char menjadi faktor)

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.

3. Eksplorasi Data

Cek Missing Value

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.
Data summary
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              
## 

Distribusi target “y”

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).

4. Tabel kontingensi antar peubah

library(gmodels)
## Warning: package 'gmodels' was built under R version 4.5.2

Tabel kontingensi antara “job” dan “y”

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% |           | 
## ---------------|-----------|-----------|-----------|
## 
## 

Tabel kontingensi antara “marital” dan “y”

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% |           | 
## -------------------|-----------|-----------|-----------|
## 
## 

Tabel kontingensi antara “education” dan “y”

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% |           | 
## ---------------------|-----------|-----------|-----------|
## 
## 

Tabel kontingensi antara “housing” dan “y”

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.

5. Perhitungan Entropy dan Information Gain

entropy <- function(counts) {
  counts <- as.numeric(counts)
  p <- counts / sum(counts)
  p <- p[p > 0]
  -sum(p * log2(p))
}

Entropy Total Target

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%.

6. Model 1 : Pemodelan dengan Classification Tree tanpa peubah Duration

Hapus Peubah duration pada Data Training & Testing

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

Membangun model classification tree

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.

Visualisasi Tree

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.

7. Evaluasi Model

Evaluasi terhadap data training

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

Prediksi kelas pada data testing

pred_class <- predict(model_tree,
                      newdata = data_test,
                      type = "class")

Confusion Matrix

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             
## 

F1 Score

precision <- cm$byClass["Pos Pred Value"]
recall <- cm$byClass["Sensitivity"]
F1 <- 2 * (precision * recall) / (precision + recall)
F1
## Pos Pred Value 
##      0.3063683

ROC dan AUC

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.

8. Model 2 : Penanganan Ketidakseimbangan Kelas dengan 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”

Membangun model SMOTE

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.

Visualisasi model SMOTE

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.

9. Evaluasi model SMOTE

Evaluasi model SMOTE terhadap data training

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

Prediksi kelas terbaru pada data testing

pred_smote <- predict(model_tree_smote,
                      newdata = data_test,
                      type = "class")

Confusion Matrix SMOTE

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             
## 

F1 Score SMOTE

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

ROC dan AUC Smote

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.

10. Kesimpulan

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.

Pruning

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.