1 Objective

Pada kesempatan kali ini, saya akan mencoba untuk melakukan prediksi terhadap pasien penyakit jantung pada suatu rumah sakit yang akan diprediksi sakit atau tidak berdasarkan kategori dari beberapa variabel. Algoritma yang digunakan yaitu logistic regression, K-Nearest Neighboor, Naive Bayes, Decision Tree dan Random Forest yang merupakan supervised learning.

2 Library and Setup

2.1 Dataset

Dataset yang digunakan pada kesempatan kali ini adalah data mengenai pasien yang terkena penyakit jantung berdasarkan beberapa karakteristik yang menyertai, data diambil dari Kaggle - Heart Disease UCI yang dapat diunduh pada link berikut https://www.kaggle.com/ronitf/heart-disease-uci

2.2 Load Library

library(dplyr)
## Warning: package 'dplyr' was built under R version 4.0.4
## 
## 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
library(class)
library(tidyr)
## Warning: package 'tidyr' was built under R version 4.0.4
library(caret)
## Warning: package 'caret' was built under R version 4.0.5
## Loading required package: lattice
## Loading required package: ggplot2
## Warning: package 'ggplot2' was built under R version 4.0.4
library(e1071)
## Warning: package 'e1071' was built under R version 4.0.5
library(ROCR)
## Warning: package 'ROCR' was built under R version 4.0.5
library(partykit)
## Warning: package 'partykit' was built under R version 4.0.5
## Loading required package: grid
## Loading required package: libcoin
## Warning: package 'libcoin' was built under R version 4.0.5
## Loading required package: mvtnorm
## Warning: package 'mvtnorm' was built under R version 4.0.3
library(randomForest)
## Warning: package 'randomForest' was built under R version 4.0.5
## randomForest 4.6-14
## Type rfNews() to see new features/changes/bug fixes.
## 
## Attaching package: 'randomForest'
## The following object is masked from 'package:ggplot2':
## 
##     margin
## The following object is masked from 'package:dplyr':
## 
##     combine
options(scipen = 9999)

2.3 Input Data

jantung <- read.csv("dataset/heart.csv")
str(jantung)
## 'data.frame':    303 obs. of  14 variables:
##  $ ï..age  : int  63 37 41 56 57 57 56 44 52 57 ...
##  $ sex     : int  1 1 0 1 0 1 0 1 1 1 ...
##  $ cp      : int  3 2 1 1 0 0 1 1 2 2 ...
##  $ trestbps: int  145 130 130 120 120 140 140 120 172 150 ...
##  $ chol    : int  233 250 204 236 354 192 294 263 199 168 ...
##  $ fbs     : int  1 0 0 0 0 0 0 0 1 0 ...
##  $ restecg : int  0 1 0 1 1 1 0 1 1 1 ...
##  $ thalach : int  150 187 172 178 163 148 153 173 162 174 ...
##  $ exang   : int  0 0 0 0 1 0 0 0 0 0 ...
##  $ oldpeak : num  2.3 3.5 1.4 0.8 0.6 0.4 1.3 0 0.5 1.6 ...
##  $ slope   : int  0 0 2 2 2 1 1 2 2 2 ...
##  $ ca      : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ thal    : int  1 2 2 2 2 1 2 3 3 2 ...
##  $ target  : int  1 1 1 1 1 1 1 1 1 1 ...

Informasi penting dalam data :

ï..age : dalam beberapa tahun

sex : (1 = laki-laki; 0 = perempuan)

cp : tipe nyeri yang paling parah

trestbps : melacak tekanan darah(dalam mm Hg saat masuk ke rumah sakit)

chol : kolestoral dalam mg / dl

fbs : (gula darah puasa> 120 mg / dl) (1 = benar; 0 = salah)

restecg : mengembalikan hasil elektrokardiografi

thalach : denyut jantung maksimum tercapai

exang : exercise induced angina (1 = ya; 0 = tidak)

oldpeak : ST depresi yang disebabkan oleh olahraga relatif terhadap istirahat

slope : kemiringan segmen ST latihan puncak

ca : jumlah pembuluh darah utama (0-3) diwarnai dengan fluoroskopi

thal : 3 = normal; 6 = cacat tetap; 7 = cacat yang dapat dibalik

target : 1 = sakit atau 0 = tidak sakit

head(jantung)
##   ï..age sex cp trestbps chol fbs restecg thalach exang oldpeak slope ca thal
## 1     63   1  3      145  233   1       0     150     0     2.3     0  0    1
## 2     37   1  2      130  250   0       1     187     0     3.5     0  0    2
## 3     41   0  1      130  204   0       0     172     0     1.4     2  0    2
## 4     56   1  1      120  236   0       1     178     0     0.8     2  0    2
## 5     57   0  0      120  354   0       1     163     1     0.6     2  0    2
## 6     57   1  0      140  192   0       1     148     0     0.4     1  0    1
##   target
## 1      1
## 2      1
## 3      1
## 4      1
## 5      1
## 6      1

2.4 Struktur Data

Cek terlebih dahulu struktur dataset. Agar kita tahu gambaran struktur dataset dari masing-masing variabel yang akan kita gunakan. Sehingga dapat kita ketahui apakah sudah sesuai atau masih perlu ada yang dirubah, maka dapat dilakukan pada tahap selanjutnya.

glimpse(jantung)
## Rows: 303
## Columns: 14
## $ ï..age   <int> 63, 37, 41, 56, 57, 57, 56, 44, 52, 57, 54, 48, 49, 64, 58, 5~
## $ sex      <int> 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1~
## $ cp       <int> 3, 2, 1, 1, 0, 0, 1, 1, 2, 2, 0, 2, 1, 3, 3, 2, 2, 3, 0, 3, 0~
## $ trestbps <int> 145, 130, 130, 120, 120, 140, 140, 120, 172, 150, 140, 130, 1~
## $ chol     <int> 233, 250, 204, 236, 354, 192, 294, 263, 199, 168, 239, 275, 2~
## $ fbs      <int> 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0~
## $ restecg  <int> 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1~
## $ thalach  <int> 150, 187, 172, 178, 163, 148, 153, 173, 162, 174, 160, 139, 1~
## $ exang    <int> 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0~
## $ oldpeak  <dbl> 2.3, 3.5, 1.4, 0.8, 0.6, 0.4, 1.3, 0.0, 0.5, 1.6, 1.2, 0.2, 0~
## $ slope    <int> 0, 0, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 0, 2, 2, 1~
## $ ca       <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0~
## $ thal     <int> 1, 2, 2, 2, 2, 1, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3~
## $ target   <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1~

2.5 Data Manipulation

Dalam hal ini terdapat beberapa variabel yang digunakan tetapi terdapat ketidaksesuaian tipe data, maka harus disesuaikan terlebih dahulu.

jantung <- jantung %>% 
  select(-ï..age) %>%
  mutate_if(is.integer, as.factor) %>% 
  mutate(sex = factor(sex, levels = c(0,1), 
                      labels = c("Female", "Male")),
         fbs =factor(fbs, levels = c(0,1), 
                     labels = c("False", "True")),
         exang = factor(exang, levels = c(0,1), 
                        labels = c("Tidak", "Iya")),
         target = factor(target, levels = c(0,1), 
                        labels = c("Sehat", "Tidak Sehat")))
glimpse(jantung)
## Rows: 303
## Columns: 13
## $ sex      <fct> Male, Male, Female, Male, Female, Male, Female, Male, Male, M~
## $ cp       <fct> 3, 2, 1, 1, 0, 0, 1, 1, 2, 2, 0, 2, 1, 3, 3, 2, 2, 3, 0, 3, 0~
## $ trestbps <fct> 145, 130, 130, 120, 120, 140, 140, 120, 172, 150, 140, 130, 1~
## $ chol     <fct> 233, 250, 204, 236, 354, 192, 294, 263, 199, 168, 239, 275, 2~
## $ fbs      <fct> True, False, False, False, False, False, False, False, True, ~
## $ restecg  <fct> 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1~
## $ thalach  <fct> 150, 187, 172, 178, 163, 148, 153, 173, 162, 174, 160, 139, 1~
## $ exang    <fct> Tidak, Tidak, Tidak, Tidak, Iya, Tidak, Tidak, Tidak, Tidak, ~
## $ oldpeak  <dbl> 2.3, 3.5, 1.4, 0.8, 0.6, 0.4, 1.3, 0.0, 0.5, 1.6, 1.2, 0.2, 0~
## $ slope    <fct> 0, 0, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 0, 2, 2, 1~
## $ ca       <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0~
## $ thal     <fct> 1, 2, 2, 2, 2, 1, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3~
## $ target   <fct> Tidak Sehat, Tidak Sehat, Tidak Sehat, Tidak Sehat, Tidak Seh~

Tipe data dalam dataset sudah sesuai, kemudian cek apakah terdapat missing value :

anyNA(jantung)
## [1] FALSE

Tidak ada nilai Missing Value yang ditemukan

2.6 Pre-Processing Data

2.6.1 Cek Proporsi Target

Hal yang dilakukan sebelum melakukan pemodelan, lakukan terlebih dahulu proporsi dari target variabel yang kita gunakan. dalam hal ini variabel yang digunakan adalah target.

prop.table(table(jantung$target))
## 
##       Sehat Tidak Sehat 
##   0.4554455   0.5445545
table(jantung$target)
## 
##       Sehat Tidak Sehat 
##         138         165

Dapat kita lihat dari proporsi kedua kelas tersebut menghasilkan proporsi yang seimbang, sehingga kita tidak perlu melakukan pre-processing tambahan untuk menyeimbangkan proporsi antar dua kelas target dari variabel yang kita gunakan.

2.6.2 Spliting train-test

Selanjutnya adalah melakukan splitting data menjadi data train dan data test. Splitting data train dan test ini bertujuan untuk membuat model yang digunakan yaitu data test dan data test tujuannya untuk menguji model terhadap unseen data. Splitting data train dan test ini menggunakan proporsi 80% untuk data train dan 20% untuk data test.

RNGkind(sample.kind = "Rounding")
## Warning in RNGkind(sample.kind = "Rounding"): non-uniform 'Rounding' sampler
## used
set.seed(418)
indextrain <- sample(nrow(jantung),
                  nrow(jantung)*0.8)

jantung.train <- jantung[indextrain,]
jantung.test <- jantung[-indextrain,]

2.6.3 Re-check Proporsi Target Terhadap Data Train

prop.table(table(jantung.train$target))
## 
##       Sehat Tidak Sehat 
##   0.4752066   0.5247934

Hasil yang diperoleh bahwa proporsi dari data train masih seimbang dan tidak perlu pre-processing tambahan lagi untuk data train yang kita gunakan.

3 Logistic Regression

3.1 Modelling

Dalam melakukan pemodelan kali ini menggunakan Logistc Regression. Pemodelan menggunakan fungsi glm() dalam membuat model Logistic Regression.

Dalam pembuatan atau analisa model logistic Regression, terlebih dahulu melakukan pengecekan terhadap korelasi chance_of_admit dengan variabel-variabel dari dataset. Dan variabel yang digunakan merupakan variabel yang dianggap mempengaruhi target variabel, dimana variabel yang menjadi responnya adalah variabel target.

model_jantung <- glm(formula = target ~ sex + cp + fbs + exang + oldpeak + slope + ca + thal, 
                     family = "binomial",
                     data = jantung.train)
summary(model_jantung)
## 
## Call:
## glm(formula = target ~ sex + cp + fbs + exang + oldpeak + slope + 
##     ca + thal, family = "binomial", data = jantung.train)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -2.6772  -0.3484   0.1132   0.4849   2.9536  
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)    
## (Intercept)   12.9559  1455.3979   0.009 0.992897    
## sexMale       -1.4911     0.5649  -2.640 0.008303 ** 
## cp1            0.8804     0.5791   1.520 0.128466    
## cp2            2.0282     0.5493   3.692 0.000222 ***
## cp3            1.9919     0.7735   2.575 0.010019 *  
## fbsTrue        0.6820     0.6086   1.121 0.262464    
## exangIya      -1.1585     0.4921  -2.354 0.018564 *  
## oldpeak       -0.4127     0.2510  -1.645 0.100060    
## slope1        -0.6859     0.8397  -0.817 0.413989    
## slope2         0.5820     0.9086   0.641 0.521837    
## ca1           -2.0677     0.5572  -3.711 0.000206 ***
## ca2           -2.6754     0.7824  -3.420 0.000627 ***
## ca3           -2.0375     0.9011  -2.261 0.023755 *  
## ca4            1.0094     1.5377   0.656 0.511523    
## thal1        -10.5572  1455.3981  -0.007 0.994212    
## thal2        -10.5196  1455.3978  -0.007 0.994233    
## thal3        -11.8192  1455.3978  -0.008 0.993521    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 334.89  on 241  degrees of freedom
## Residual deviance: 159.26  on 225  degrees of freedom
## AIC: 193.26
## 
## Number of Fisher Scoring iterations: 14

3.1.1 Model Fitting

Pada pemodelan yang pertama, masih banyak variabel prediktor yang tidak signifikan terhadap target variabel, oleh karena itu kita akan coba melakukan model fitting menggunakan metode stepwise - backward. Sehingga diperoleh model sebagai berikut

backward_jantung <- step(object = model_jantung,
                         direction = "backward",
                         trace = F)
summary(backward_jantung)
## 
## Call:
## glm(formula = target ~ sex + cp + exang + oldpeak + slope + ca + 
##     thal, family = "binomial", data = jantung.train)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -2.7303  -0.3589   0.1093   0.4955   2.8958  
## 
## Coefficients:
##              Estimate Std. Error z value  Pr(>|z|)    
## (Intercept)   12.9408  1455.3979   0.009  0.992906    
## sexMale       -1.4599     0.5616  -2.600  0.009333 ** 
## cp1            0.9050     0.5730   1.579  0.114271    
## cp2            2.1583     0.5396   4.000 0.0000635 ***
## cp3            2.1137     0.7657   2.760  0.005775 ** 
## exangIya      -1.0607     0.4799  -2.210  0.027073 *  
## oldpeak       -0.4518     0.2491  -1.814  0.069747 .  
## slope1        -0.7858     0.8329  -0.943  0.345454    
## slope2         0.4670     0.9058   0.516  0.606201    
## ca1           -2.0297     0.5475  -3.707  0.000209 ***
## ca2           -2.5424     0.7649  -3.324  0.000887 ***
## ca3           -1.9492     0.8878  -2.196  0.028122 *  
## ca4            1.2397     1.6143   0.768  0.442503    
## thal1        -10.4020  1455.3981  -0.007  0.994297    
## thal2        -10.4033  1455.3978  -0.007  0.994297    
## thal3        -11.6919  1455.3978  -0.008  0.993590    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 334.89  on 241  degrees of freedom
## Residual deviance: 160.54  on 226  degrees of freedom
## AIC: 192.54
## 
## Number of Fisher Scoring iterations: 14

3.2 Prediksi

Dengan menggunakan hasil modelling backward_jantung yang merupakan hasil dari stepwise backward selanjutnya akan dihitung prediksi probability sakit atau tidak untuk jantung.test.

jantung.test$pred.Risk <- predict(backward_jantung, newdata = jantung.test, type = "response")
head(jantung.test)
##       sex cp trestbps chol   fbs restecg thalach exang oldpeak slope ca thal
## 8    Male  1      120  263 False       1     173 Tidak     0.0     2  0    3
## 11   Male  0      140  239 False       1     160 Tidak     1.2     2  0    2
## 14   Male  3      110  211 False       0     144   Iya     1.8     1  0    2
## 17 Female  2      120  340 False       1     172 Tidak     0.0     2  0    2
## 27   Male  2      150  212  True       1     157 Tidak     1.6     2  0    2
## 35   Male  3      125  213 False       0     125   Iya     1.4     2  1    2
##         target pred.Risk
## 8  Tidak Sehat 0.7615005
## 11 Tidak Sehat 0.7315103
## 14 Tidak Sehat 0.6298010
## 17 Tidak Sehat 0.9943067
## 27 Tidak Sehat 0.9516575
## 35 Tidak Sehat 0.4837961

Selanjutkan lakukan klasifikasi jantung.test berdasarkan pred.Risk dengan syarat lebih besar dari 0.5.

jantung.test$pred.Label <- ifelse(jantung.test$pred.Risk > 0.5, yes = "Tidak Sehat", no = "Sehat")
jantung.test$pred.Label <- as.factor(jantung.test$pred.Label)

3.3 Hasil Prediksi

Hasil prediksi berdasarkan klasifikasi yang telah dibuat sebelumnya.

jantung.test %>% 
  select(target, pred.Label) %>% 
  head(10)
##         target  pred.Label
## 8  Tidak Sehat Tidak Sehat
## 11 Tidak Sehat Tidak Sehat
## 14 Tidak Sehat Tidak Sehat
## 17 Tidak Sehat Tidak Sehat
## 27 Tidak Sehat Tidak Sehat
## 35 Tidak Sehat       Sehat
## 36 Tidak Sehat Tidak Sehat
## 42 Tidak Sehat Tidak Sehat
## 44 Tidak Sehat Tidak Sehat
## 51 Tidak Sehat Tidak Sehat

3.4 Model Evaluation

Confusion Matrix digunakan dalam mengevaluasi model yang telah dibuat.

model_logistic <- confusionMatrix(jantung.test$pred.Label, jantung.test$target, positive = "Tidak Sehat")

model_logistic
## Confusion Matrix and Statistics
## 
##              Reference
## Prediction    Sehat Tidak Sehat
##   Sehat          19           3
##   Tidak Sehat     4          35
##                                           
##                Accuracy : 0.8852          
##                  95% CI : (0.7778, 0.9526)
##     No Information Rate : 0.623           
##     P-Value [Acc > NIR] : 0.000004712     
##                                           
##                   Kappa : 0.7536          
##                                           
##  Mcnemar's Test P-Value : 1               
##                                           
##             Sensitivity : 0.9211          
##             Specificity : 0.8261          
##          Pos Pred Value : 0.8974          
##          Neg Pred Value : 0.8636          
##              Prevalence : 0.6230          
##          Detection Rate : 0.5738          
##    Detection Prevalence : 0.6393          
##       Balanced Accuracy : 0.8736          
##                                           
##        'Positive' Class : Tidak Sehat     
## 

Berdasarkan hasil model evaluation dengan menggunakan confusion matrix di atas, diperoleh hasil bahwa kemampuan model dalam menebak target “1” atau “Tidak Sehat” sebesar 92,11% untuk Recall/Sensitivity, 82,61% untuk Specificity, 89,74% untuk Precision/Pos Pred Value dan 88,52% untuk Accuracy.

4 K-Nearest Neighbour

4.1 Pre-Processing Data

Membuat terlebih dahulu variabel dummy dari data-data kategori yang akan digunakan dalam klasifikasi.

data_dummy <- dummyVars(formula = ~ target + sex + cp + fbs + exang + oldpeak + slope + ca + thal, data = jantung)
data_dummy <- data.frame(predict(data_dummy, newdata = jantung))
str(data_dummy)
## 'data.frame':    303 obs. of  25 variables:
##  $ target.Sehat      : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ target.Tidak.Sehat: num  1 1 1 1 1 1 1 1 1 1 ...
##  $ sex.Female        : num  0 0 1 0 1 0 1 0 0 0 ...
##  $ sex.Male          : num  1 1 0 1 0 1 0 1 1 1 ...
##  $ cp.0              : num  0 0 0 0 1 1 0 0 0 0 ...
##  $ cp.1              : num  0 0 1 1 0 0 1 1 0 0 ...
##  $ cp.2              : num  0 1 0 0 0 0 0 0 1 1 ...
##  $ cp.3              : num  1 0 0 0 0 0 0 0 0 0 ...
##  $ fbs.False         : num  0 1 1 1 1 1 1 1 0 1 ...
##  $ fbs.True          : num  1 0 0 0 0 0 0 0 1 0 ...
##  $ exang.Tidak       : num  1 1 1 1 0 1 1 1 1 1 ...
##  $ exang.Iya         : num  0 0 0 0 1 0 0 0 0 0 ...
##  $ oldpeak           : num  2.3 3.5 1.4 0.8 0.6 0.4 1.3 0 0.5 1.6 ...
##  $ slope.0           : num  1 1 0 0 0 0 0 0 0 0 ...
##  $ slope.1           : num  0 0 0 0 0 1 1 0 0 0 ...
##  $ slope.2           : num  0 0 1 1 1 0 0 1 1 1 ...
##  $ ca.0              : num  1 1 1 1 1 1 1 1 1 1 ...
##  $ ca.1              : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ ca.2              : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ ca.3              : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ ca.4              : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ thal.0            : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ thal.1            : num  1 0 0 0 0 1 0 0 0 0 ...
##  $ thal.2            : num  0 1 1 1 1 0 1 0 0 1 ...
##  $ thal.3            : num  0 0 0 0 0 0 0 1 1 0 ...

Menghapus variabel dummy yang di variabel sebelumnya terdapat 2 kategori

data_dummy$target.Sehat <- NULL
data_dummy$sex.Female <- NULL
data_dummy$fbs.False <- NULL
data_dummy$exang.Tidak <- NULL

4.1.1 Melihat Nama-nama Variabel dari data dummy

names(data_dummy)
##  [1] "target.Tidak.Sehat" "sex.Male"           "cp.0"              
##  [4] "cp.1"               "cp.2"               "cp.3"              
##  [7] "fbs.True"           "exang.Iya"          "oldpeak"           
## [10] "slope.0"            "slope.1"            "slope.2"           
## [13] "ca.0"               "ca.1"               "ca.2"              
## [16] "ca.3"               "ca.4"               "thal.0"            
## [19] "thal.1"             "thal.2"             "thal.3"

4.1.2 Splitting Data Train dan Test

Membentuk data training dan data testing dari data_dummy yang telah terbentuk.

RNGkind(sample.kind = "Rounding")
## Warning in RNGkind(sample.kind = "Rounding"): non-uniform 'Rounding' sampler
## used
set.seed(418)
indextrain_dummy <- sample(nrow(data_dummy),
                           nrow(data_dummy)*0.8)

data_dummy.train <- data_dummy[indextrain_dummy,]
data_dummy.test <- data_dummy[-indextrain_dummy,]
#prediktor
jantung.train_x <- data_dummy.train %>% 
  select(-target.Tidak.Sehat)

jantung.test_x <- data_dummy.train %>% 
  select(-target.Tidak.Sehat)

#target
jantung.train_y <- data_dummy.train %>% 
  select(target.Tidak.Sehat)

jantung.test_y <- data_dummy.train %>% 
  select(target.Tidak.Sehat)

4.1.3 Scalling Prediktor

Data prediktor akan dilkukan scalling menggunakan z-score standarization. Data test juga harus dilakukan scalling dengan menggunakan parameter dari data train karena data tets tersebut merupakan unseen data.

jantung.train_xs <- scale(jantung.train_x)
jantung.test_xs <- scale(x = jantung.test_x,
                         center = attr(jantung.train_xs, "scaled:center"),
                         scale = attr(jantung.train_xs, "scaled:scale"))

4.2 Prediksi

4.2.1 Menemukan Optimum K

Mencari nilai optimum k ini bertujuan mengelompokkan jumlah tetangga terdekat.

round(sqrt(nrow(jantung.train)))
## [1] 16

Dalam hal ini jumlah kelas target yaitu 2 (Sehat dan Tidak Sehat). Nilai optimum k yang didapat adlaah 16, untuk menghindari terjadinya seri maka nilai k dinaikkan menjadi 17.

jantung.pred <- knn(train = jantung.train_xs,
                    test = jantung.test_xs,
                    cl = jantung.train_y$target.Tidak.Sehat,
                    k = 17)
dim(jantung.train_xs)
## [1] 242  20
dim(jantung.test_xs)
## [1] 242  20

4.2.2 Cek Hasil Prediksi

head(jantung.pred)
## [1] 1 0 1 1 1 0
## Levels: 0 1

4.2.3 Model Evaluation

Menggunakan Confusion Matrix untuk melakukan model evaluasi

model_knn <- confusionMatrix(data = as.factor(jantung.pred), reference = as.factor(jantung.train_y$target.Tidak.Sehat), positive = "1")

model_knn
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1
##          0  94  15
##          1  21 112
##                                              
##                Accuracy : 0.8512             
##                  95% CI : (0.8, 0.8936)      
##     No Information Rate : 0.5248             
##     P-Value [Acc > NIR] : <0.0000000000000002
##                                              
##                   Kappa : 0.701              
##                                              
##  Mcnemar's Test P-Value : 0.4047             
##                                              
##             Sensitivity : 0.8819             
##             Specificity : 0.8174             
##          Pos Pred Value : 0.8421             
##          Neg Pred Value : 0.8624             
##              Prevalence : 0.5248             
##          Detection Rate : 0.4628             
##    Detection Prevalence : 0.5496             
##       Balanced Accuracy : 0.8496             
##                                              
##        'Positive' Class : 1                  
## 

Berdasarkan hasil model evaluation dengan menggunakan confusion matrix di atas, diperoleh hasil bahwa kemampuan model dalam menebak target “1” atau “Tidak Sehat” sebesar 88,19% untuk Recall/Sensitivity, 81,74% untuk Specificity, 84,21% untuk Precision/Pos Pred Value dan 85,12% untuk Accuracy.

length(jantung.pred)
## [1] 242
length(jantung.train_y$target.Tidak.Sehat)
## [1] 242

Dari Dataset Heart tersebut, selanjutnya kan dipakai untuk membuat model klasifikasi lain dengan menggunakan Naive Bayes, Decision Tree, Random Forest.

5 Naive Bayes

Data yang digunakan adalah data hasil cross validation sebelumnya

5.1 Modelling

jantung_naivebayes <- naiveBayes(x = jantung.train %>% 
  select(-target), y = jantung.train$target, laplace = 1)

5.2 Model Evaluation

5.2.1 Prediksi

Menghitung prediksi terhadap model klasifikasi naiveBayes.

jantung_predClass <- predict(object = jantung_naivebayes, 
                             newdata = jantung.test,
                             type = "class")
table(jantung_predClass)
## jantung_predClass
##       Sehat Tidak Sehat 
##          22          39
head(jantung_predClass)
## [1] Tidak Sehat Tidak Sehat Sehat       Tidak Sehat Tidak Sehat Sehat      
## Levels: Sehat Tidak Sehat

Data diatas memperlihatkan bahwa pasien yang sehat sebanyak 22 orang dan pasien yang tidak sehat sebanyak 39 orang. Bagaiman dengan confusion matrix nya.

model_naivebayes <- confusionMatrix(data = jantung_predClass,
                                    reference = jantung.test$target,
                                    positive = "Tidak Sehat")

model_naivebayes
## Confusion Matrix and Statistics
## 
##              Reference
## Prediction    Sehat Tidak Sehat
##   Sehat          18           4
##   Tidak Sehat     5          34
##                                           
##                Accuracy : 0.8525          
##                  95% CI : (0.7383, 0.9302)
##     No Information Rate : 0.623           
##     P-Value [Acc > NIR] : 0.0000748       
##                                           
##                   Kappa : 0.6832          
##                                           
##  Mcnemar's Test P-Value : 1               
##                                           
##             Sensitivity : 0.8947          
##             Specificity : 0.7826          
##          Pos Pred Value : 0.8718          
##          Neg Pred Value : 0.8182          
##              Prevalence : 0.6230          
##          Detection Rate : 0.5574          
##    Detection Prevalence : 0.6393          
##       Balanced Accuracy : 0.8387          
##                                           
##        'Positive' Class : Tidak Sehat     
## 

Berdasarkan hasil model evaluation dengan menggunakan confusion matrix di atas, diperoleh hasil bahwa kemampuan model dalam menebak target “1” atau “Tidak Sehat” sebesar 89,47% untuk Recall/Sensitivity, 78,26% untuk Specificity, 87,18% untuk Precision/Pos Pred Value dan 85,25% untuk Accuracy.

5.3 ROC dan AUC

Melihat ROC dan AUC untuk melihat model bagus memprediksi atau tidak.

#mengambil hasil prediksi probability
jantung_predProb <- predict(jantung_naivebayes, newdata = jantung.test, type = "raw")

head(jantung_predProb)
##             Sehat Tidak Sehat
## [1,] 0.0123089141   0.9876911
## [2,] 0.1643566902   0.8356433
## [3,] 0.8405785714   0.1594214
## [4,] 0.0001127051   0.9998873
## [5,] 0.0093486265   0.9906514
## [6,] 0.8666469679   0.1333530

Selanjutnya membuat ROC dengan menyiapkan objek prediction.

#buat objek prediction
jantung_roc <- prediction(predictions = jantung_predProb[,1],
                          labels = as.numeric(jantung.test$target == "Sehat"))

#buat performance dari objek prediction
perf <- performance(prediction.obj = jantung_roc, measure = "tpr", x.measure = "fpr")

#buat plot
plot(perf)
abline(0,1, lty = 2)

5.4 Area Under ROC Curve (AUC)

Selanjutnya kita mencari AUC. Semakin mendekati 1, semakin bagus performa modelnya.

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

Nilai AUC dari Naive Bayes mendekati 1, artinya model kita mampu mengklasifikasikan kelas positif dengan baik tetapi kelas negatif dengan tidak baik.

6 Decision Tree

Klasifikasi menggunakan Decision Tree

jantung_dtree <- ctree(formula = target ~ ., data = jantung.train,
                       control = ctree_control(mincriterion = 0.95,
                                               minsplit = 0,
                                               minbucket = 0))
plot(jantung_dtree, type = "simple")

Setelah dapat tree tersebut, selanjutkan lakukan evaluasi untuk melihat apakah model terjadi overfitting atau tidak.

jantung_predict <- predict(object = jantung_dtree, newdata = jantung.train %>% 
                             select(-target), type = "response")
model_decision_tree_train <- confusionMatrix(data = jantung_predict,
                                             reference = jantung.train$target, 
                                             positive = "Tidak Sehat")

model_decision_tree_train
## Confusion Matrix and Statistics
## 
##              Reference
## Prediction    Sehat Tidak Sehat
##   Sehat          83          10
##   Tidak Sehat    32         117
##                                                
##                Accuracy : 0.8264               
##                  95% CI : (0.7727, 0.872)      
##     No Information Rate : 0.5248               
##     P-Value [Acc > NIR] : < 0.00000000000000022
##                                                
##                   Kappa : 0.6489               
##                                                
##  Mcnemar's Test P-Value : 0.001194             
##                                                
##             Sensitivity : 0.9213               
##             Specificity : 0.7217               
##          Pos Pred Value : 0.7852               
##          Neg Pred Value : 0.8925               
##              Prevalence : 0.5248               
##          Detection Rate : 0.4835               
##    Detection Prevalence : 0.6157               
##       Balanced Accuracy : 0.8215               
##                                                
##        'Positive' Class : Tidak Sehat          
## 

Dari hasil confusionMatrix decision tree diatas, performa model tidak cukup baik untuk matriks Pos Pred Value atau Precision. Coba cek apakah hal tersebut terjadi overfitting.

jantung_predict_test <- predict(object = jantung_dtree,
                                newdata = jantung.test %>% 
                                  select(-target), type = "response")
model_decision_tree_test <- confusionMatrix(data = jantung_predict_test,
                                            reference = jantung.test$target, positive = "Tidak Sehat")

model_decision_tree_test
## Confusion Matrix and Statistics
## 
##              Reference
## Prediction    Sehat Tidak Sehat
##   Sehat          15           3
##   Tidak Sehat     8          35
##                                           
##                Accuracy : 0.8197          
##                  95% CI : (0.7002, 0.9064)
##     No Information Rate : 0.623           
##     P-Value [Acc > NIR] : 0.0007305       
##                                           
##                   Kappa : 0.5989          
##                                           
##  Mcnemar's Test P-Value : 0.2278000       
##                                           
##             Sensitivity : 0.9211          
##             Specificity : 0.6522          
##          Pos Pred Value : 0.8140          
##          Neg Pred Value : 0.8333          
##              Prevalence : 0.6230          
##          Detection Rate : 0.5738          
##    Detection Prevalence : 0.7049          
##       Balanced Accuracy : 0.7866          
##                                           
##        'Positive' Class : Tidak Sehat     
## 

Hasil didapatkan adalah terjadi overfitting yang tidak terlalu ekstrim antara jantung.train dan jantung.test pada matriks Pos Pred Value. Matriks Pos Pred Value di jantung.train sebesar 78.52% sedangkan pada jantung.test sebesar 81.40%.

Selanjutnya melakukan prediksi dengan menggunakan klasifikasi yaitu decision tree.

pred <- predict(jantung_dtree, jantung.test)
table(pred, jantung.test$target)
##              
## pred          Sehat Tidak Sehat
##   Sehat          15           3
##   Tidak Sehat     8          35

Hasil prediksi menggunakan Decision Tree terdapat pasien Sehat yang benar di prediksi Sehat sebanyak 15 orang dan salah diprediksi Tidak Sehat sebanyak 3 orang. Dan untuk pasien yang Tidak Sehat yang diprediksi benar **Tidak Sehat sebanyak 35 orang dan pasien yang salah diprediksi sebanyak 8 pasien.

Selanjutnya dari klasifikasi decision tree, maka akan melakukan uji coba lagi, agar dapat memperoleh nilai Pos Pred Value yang begitu tinggi dan baik. Klasifikasi yang ingin di terapkan yaitu klasifikasi Random Forest.

7 Random Forest

7.1 Modelling

RNGkind(sample.kind = "Rounding")
## Warning in RNGkind(sample.kind = "Rounding"): non-uniform 'Rounding' sampler
## used
set.seed(418)
control <- trainControl(method = "repeatedcv", number = 4, repeats = 3)


modelRF_jantung <- train(target ~ ., data = jantung.train,
                            method = "rf",
                            trainControl = control)

saveRDS(modelRF_jantung, "modelRF_jantung.RDS")
modelRF_jantung <- readRDS("modelRF_jantung.RDS")
modelRF_jantung
## Random Forest 
## 
## 242 samples
##  12 predictor
##   2 classes: 'Sehat', 'Tidak Sehat' 
## 
## No pre-processing
## Resampling: Bootstrapped (25 reps) 
## Summary of sample sizes: 242, 242, 242, 242, 242, 242, ... 
## Resampling results across tuning parameters:
## 
##   mtry  Accuracy   Kappa    
##     2   0.5848558  0.1713838
##   154   0.7540696  0.5076202
##   307   0.7362990  0.4709659
## 
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was mtry = 154.

Melihat Out of Bag Error yang dihasilkan dari klasifikasi random forest yang dihasilkan.

modelRF_jantung$finalModel
## 
## Call:
##  randomForest(x = x, y = y, mtry = param$mtry, trainControl = ..1) 
##                Type of random forest: classification
##                      Number of trees: 500
## No. of variables tried at each split: 154
## 
##         OOB estimate of  error rate: 24.38%
## Confusion matrix:
##             Sehat Tidak Sehat class.error
## Sehat          84          31   0.2695652
## Tidak Sehat    28          99   0.2204724
plot(modelRF_jantung$finalModel)
legend("topright", colnames(modelRF_jantung$finalModel$err.rate),
       col = 1:6, cex = 0.8, fill = 1:6)

Dengan mrty = 154, variabel yang didapatkan model dapat memprediksi data dengan pasien Tidak Sehat atau berpenyakit jantung sebanyak 99 orang sedangkan yang salah diprediksi pasien Sehat sebanyak 84 orang, seharusnya pasien Tidak Sehat sebanyak 28 pasien. Sedangkan pasien sehat yang diprediksi Tidak Sehat sebanyak 31 orang.

Nilai Out of Bag pada modelRF_jantung sebesar 24.38%. Dengan kata lain, akurasi model pada unseen data adalah 75,62%.

7.2 Interpretasi

Melihat prediktor apa saja yang paling berpengaruh terhadap target.

varImp(modelRF_jantung)
## rf variable importance
## 
##   only 20 most important variables shown (out of 307)
## 
##            Overall
## thal2      100.000
## oldpeak     89.518
## exangIya    86.588
## thal3       59.877
## cp2         35.013
## ca1         23.244
## slope2      19.902
## restecg1    19.240
## cp3         18.779
## ca2         16.175
## ca3         13.333
## sexMale     12.250
## slope1      11.479
## chol335      8.317
## chol237      7.960
## chol330      7.816
## cp1          7.761
## chol243      7.316
## thalach154   6.989
## thalach152   6.767

Dari tabel diatas, dapat diketahui prediktor yang paling tinggi pengaruhnya terhadap target adalah thal2 yang mendapatkan hasil overall sebanyak 100.

jantung_predict_RF <- predict(modelRF_jantung, jantung.test)

ConfusionMatrix dari klasifikasi Random Forest

confusionMatrix(data = jantung_predict_RF,
                reference = jantung.test$target, positive = "Tidak Sehat")
## Confusion Matrix and Statistics
## 
##              Reference
## Prediction    Sehat Tidak Sehat
##   Sehat          17           5
##   Tidak Sehat     6          33
##                                           
##                Accuracy : 0.8197          
##                  95% CI : (0.7002, 0.9064)
##     No Information Rate : 0.623           
##     P-Value [Acc > NIR] : 0.0007305       
##                                           
##                   Kappa : 0.6128          
##                                           
##  Mcnemar's Test P-Value : 1.0000000       
##                                           
##             Sensitivity : 0.8684          
##             Specificity : 0.7391          
##          Pos Pred Value : 0.8462          
##          Neg Pred Value : 0.7727          
##              Prevalence : 0.6230          
##          Detection Rate : 0.5410          
##    Detection Prevalence : 0.6393          
##       Balanced Accuracy : 0.8038          
##                                           
##        'Positive' Class : Tidak Sehat     
## 

Hasil confusion matrix di atas, Berdasarkan hasil interpretasi dengan menggunakan confusion matrix di atas, diperoleh hasil bahwa kemampuan model dalam menebak target “1” atau “Tidak Sehat” sebesar 86,84% untuk Recall/Sensitivity, 73,91% untuk Specificity, 84,62% untuk Precision/Pos Pred Value dan 81,97% untuk Accuracy.

8 Kesimpulan

Dari klasifikasi yang telah digunakan logistic Regression, K-Nearst Neighboor, Naive Bayes, Decision Tree dan Random Forest. Jika hasil yang diperoleh dengan menggunggulkan Pos Pred Value / Precision masing-masing yaitu :

  • Logistic Regression : 89.74%

  • Naive Bayes : 87.18%

  • K-Nearst Neighboor : 84.21%

  • Decision Tree : 78.52%

  • Random Forest : 84.62%

Jadi, untuk dapat digunakan dalam mendeteksi pasien denga resiko memiliki penyakit jantung atau dalam kondisi tidak sehat maka dapat menggunakan Logistic Regression, karena dengan Logistic Regression akan menghasilkan Pos Pred Value yang cukup tinggi dibanding dengan model klasifikasi yang lain yaitu sebesar 89.74%.