1 Latar Belakang

Melakukan prediksi atau Classification pada pasien yang Menderita penyakit jantung atau tidak berdasarkan kategori dari beberapa variabel penunjangnya, menggunakan logistik regression dan k-nearest neighbor

Data Source : Heart Disease UCI

2 Set Up

import Data to RStudio, dan pastikan data tersebut ditempatkan di folter yang sama dengan data project R.

2.1 Aktifkan Library

#Library 

library(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
library(gtools)
library(gmodels)
## Warning: package 'gmodels' was built under R version 4.1.1
library(ggplot2)
library(class)
library(tidyr)

2.2 Import Data

heart <- read.csv("heart.csv")
glimpse(heart)
## 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~

Penjelasan Data :

  • ï..age : Umur Pasian ( Tahun )
  • sex : Jenis Kelasim ( 1 = Pria , 2= Prempuan)
  • cp : Tipe Nyeri/sakit Dada
  • trestbps : Tekanan Darah (mmHg)
  • chol : Kolestrol (mg/dL)
  • fbs: gula darah puasa> 120 mg /dl (1 = benar; 0 = salah)
  • restecg : mengembalikan hasil elektrokardiografi
  • thalach : Maksimum Denyut Jantung
  • 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

2.3 Data Manipulation

menyesuaikan Type data (sex, cp, fbs, restecg, exang, slope, ca, thal, and target) sebelumnya Integer Menjadi Factor, kemudian melakukan Penyesuaian deskripsi data

heart_clean <- heart %>% 
  mutate_if(is.integer, as.factor) %>%  #merubah type data
  mutate(sex = factor(sex, levels = c(0,1), labels = c("Female", "Male")), #penyesuaian deskripsi data
         fbs =factor(fbs, levels = c(0,1), labels = c("False", "True")),
         exang = factor(exang, levels = c(0,1), labels = c("No", "Yes")),
         target = factor(target, levels = c(0,1), 
                        labels = c("Health", "Not Health")))

#cek Type data apakah data sudah sesuai?
glimpse(heart_clean)
## Rows: 303
## Columns: 14
## $ ï..age   <fct> 63, 37, 41, 56, 57, 57, 56, 44, 52, 57, 54, 48, 49, 64, 58, 5~
## $ 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> No, No, No, No, Yes, No, No, No, No, No, No, No, No, Yes, No,~
## $ 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> Not Health, Not Health, Not Health, Not Health, Not Health, N~

Selanjutnya yaitu melakukan pengecekan terhadap missing value. Missing value perlu kita cek terlebih dahulu agar tidak mengganggu dalam melakukan pemodelan nantinya.

colSums(is.na(heart_clean))
##   ï..age      sex       cp trestbps     chol      fbs  restecg  thalach 
##        0        0        0        0        0        0        0        0 
##    exang  oldpeak    slope       ca     thal   target 
##        0        0        0        0        0        0

Melakukan Cek proporsi dari target variabel Target

prop.table(table(heart_clean$target))
## 
##     Health Not Health 
##  0.4554455  0.5445545
summary(heart_clean)
##      ï..age        sex      cp         trestbps        chol        fbs     
##  58     : 19   Female: 96   0:143   120    : 37   197    :  6   False:258  
##  57     : 17   Male  :207   1: 50   130    : 36   204    :  6   True : 45  
##  54     : 16                2: 87   140    : 32   234    :  6              
##  59     : 14                3: 23   110    : 19   212    :  5              
##  52     : 13                        150    : 17   254    :  5              
##  51     : 12                        138    : 13   269    :  5              
##  (Other):212                        (Other):149   (Other):270              
##  restecg    thalach    exang        oldpeak     slope   ca      thal   
##  0:147   162    : 11   No :204   Min.   :0.00   0: 21   0:175   0:  2  
##  1:152   160    :  9   Yes: 99   1st Qu.:0.00   1:140   1: 65   1: 18  
##  2:  4   163    :  9             Median :0.80   2:142   2: 38   2:166  
##          152    :  8             Mean   :1.04           3: 20   3:117  
##          173    :  8             3rd Qu.:1.60           4:  5          
##          125    :  7             Max.   :6.20                          
##          (Other):251                                                   
##         target   
##  Health    :138  
##  Not Health:165  
##                  
##                  
##                  
##                  
## 

3 Modelling

Splitting Train-test

Membagi data heart menjadi data train dan test, dengan pembagian 80% train dan 20% test

RNGkind(sample.kind =  "Rounding")
## Warning in RNGkind(sample.kind = "Rounding"): non-uniform 'Rounding' sampler
## used
set.seed(100)

index <- sample(nrow(heart_clean), 
                nrow(heart_clean) *0.8) #80% 

heart_train <- heart_clean[index, ] 
heart_test <- heart_clean[-index, ]

3.1 Logistic Regression

3.1.1 Cross Validation

membuat permodelan menggunakan regresi logistik, dengan variable target menjadi variable responsnya, karena variabel tersebut mempengaruhi target variabel

modelheart <- glm(formula = target ~ sex+cp+fbs+exang+oldpeak+slope+ca+thal, 
                  data = heart_train, 
                  family = "binomial")

3.1.2 Membuat Model

disini saya menggunakan metode stepwise karena masih banyak variabel prediktor yang tidak signifikan terhadap target variabel

model_back <- step(modelheart, 
                   direction = "backward")
## Start:  AIC=175.96
## target ~ sex + cp + fbs + exang + oldpeak + slope + ca + thal
## 
##           Df Deviance    AIC
## - fbs      1   142.65 174.65
## - slope    2   145.29 175.29
## <none>         141.96 175.96
## - thal     3   150.02 178.02
## - exang    1   146.66 178.66
## - oldpeak  1   151.18 183.18
## - sex      1   151.98 183.98
## - ca       4   166.60 192.60
## - cp       3   168.97 196.97
## 
## Step:  AIC=174.65
## target ~ sex + cp + exang + oldpeak + slope + ca + thal
## 
##           Df Deviance    AIC
## - slope    2   145.66 173.66
## <none>         142.65 174.65
## - thal     3   150.65 176.65
## - exang    1   147.37 177.37
## - oldpeak  1   152.11 182.11
## - sex      1   152.30 182.30
## - ca       4   166.64 190.64
## - cp       3   172.95 198.95
## 
## Step:  AIC=173.66
## target ~ sex + cp + exang + oldpeak + ca + thal
## 
##           Df Deviance    AIC
## <none>         145.66 173.66
## - thal     3   154.60 176.60
## - exang    1   151.52 177.52
## - sex      1   153.42 179.42
## - ca       4   166.98 186.98
## - oldpeak  1   166.33 192.33
## - cp       3   176.03 198.03
summary(model_back)
## 
## Call:
## glm(formula = target ~ sex + cp + exang + oldpeak + ca + thal, 
##     family = "binomial", data = heart_train)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -2.6997  -0.2954   0.1324   0.4566   2.5844  
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)    
## (Intercept)    0.6854     3.5054   0.196 0.844993    
## sexMale       -1.4212     0.5307  -2.678 0.007409 ** 
## cp1            1.7022     0.6150   2.768 0.005643 ** 
## cp2            2.4105     0.5680   4.244 2.20e-05 ***
## cp3            3.4547     0.9737   3.548 0.000388 ***
## exangYes      -1.1908     0.4951  -2.405 0.016170 *  
## oldpeak       -1.1692     0.2904  -4.026 5.67e-05 ***
## ca1           -1.9377     0.5198  -3.728 0.000193 ***
## ca2           -2.1905     0.8871  -2.469 0.013533 *  
## ca3           -1.5541     0.9675  -1.606 0.108212    
## ca4           14.1969  1281.4345   0.011 0.991160    
## thal1          2.9101     3.6180   0.804 0.421194    
## thal2          1.9429     3.5012   0.555 0.578935    
## thal3          0.7945     3.5062   0.227 0.820730    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 332.24  on 241  degrees of freedom
## Residual deviance: 145.66  on 228  degrees of freedom
## AIC: 173.66
## 
## Number of Fisher Scoring iterations: 15

3.1.3 Prediksi

menggunakan model_back yaitu hasil dari stepwise, yang akan dicoba coba prediksi menggunakan data test yang sudah kita miliki.

heart_test$prob_heart<-predict(object = model_back, 
                               type = "response", 
                               newdata = heart_test)

Classification result predict with ifelse function

heart_test$pred_label <- factor(ifelse(heart_test$prob_heart > 0.5, "Not Health","Health"))
heart_test[ c("pred_label", "target")]

melihats peluang prediksi data

ggplot(heart_test, aes(x=prob_heart)) +
  geom_density() +
  theme_minimal()

value =

  • <0.5 (Not Heart Disease)
  • >0.5 (Heart Disease)
    sehingga dari grafik diatas dapat disumpulkan bahwa hasil prediksinya lebih >0.5 (condong ke 1) yang artinya Not Health

3.1.4 Confusion Matriks

library(caret)
## Loading required package: lattice
model_back_eval <- confusionMatrix(heart_test$pred_label, 
                                   heart_test$target, 
                                   positive = "Not Health")
model_back_eval
## Confusion Matrix and Statistics
## 
##             Reference
## Prediction   Health Not Health
##   Health         23          6
##   Not Health      8         24
##                                          
##                Accuracy : 0.7705         
##                  95% CI : (0.645, 0.8685)
##     No Information Rate : 0.5082         
##     P-Value [Acc > NIR] : 2.346e-05      
##                                          
##                   Kappa : 0.5414         
##                                          
##  Mcnemar's Test P-Value : 0.7893         
##                                          
##             Sensitivity : 0.8000         
##             Specificity : 0.7419         
##          Pos Pred Value : 0.7500         
##          Neg Pred Value : 0.7931         
##              Prevalence : 0.4918         
##          Detection Rate : 0.3934         
##    Detection Prevalence : 0.5246         
##       Balanced Accuracy : 0.7710         
##                                          
##        'Positive' Class : Not Health     
## 

Summary Evaluation Logistic Regression =

-Accuracy : 0.7705 –> 77% seberapa mampu model menebak dengan benar target (Health & Not Health).

-Sensitivity (Recall) : 0.8000 –> 80% dari semua data aktual yang positif, seberapa mampu proporsi model saya menebak benar.

-Specificity : 0.7419 –> 74,1% dari semua data aktual yang negatif, seberapa mampu proporsi model saya menebak yang benar.

-Pos Pred (Precision) : 0.7500 –> 75% dari semua hasil prediksi, seberapa mampu model saya dapat menebak benar kelas positif.

3.2 K-Nearest Neighbor

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

knn <- dummyVars(" ~target+sex+cp+fbs+exang+oldpeak+slope+ca+thal", 
                 data = heart_clean)

knn <- data.frame(predict(knn, 
                          newdata = heart_clean)
                  )
str(knn)
## 'data.frame':    303 obs. of  25 variables:
##  $ target.Health    : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ target.Not.Health: 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.No         : num  1 1 1 1 0 1 1 1 1 1 ...
##  $ exang.Yes        : 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 ...
knn$target.Health <- NULL
knn$sex.Female <- NULL
knn$fbs.False <- NULL
knn$exang.No <- NULL
names(knn)
##  [1] "target.Not.Health" "sex.Male"          "cp.0"             
##  [4] "cp.1"              "cp.2"              "cp.3"             
##  [7] "fbs.True"          "exang.Yes"         "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"
names(heart_clean)
##  [1] "ï..age"   "sex"      "cp"       "trestbps" "chol"     "fbs"     
##  [7] "restecg"  "thalach"  "exang"    "oldpeak"  "slope"    "ca"      
## [13] "thal"     "target"

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

set.seed(110)
knn_train <- knn[index,2:21]
knn_test <- knn[-index,2:21]

knn_train_label <- knn[index,1]
knn_test_label <- knn[-index,1]
round(sqrt(nrow(knn_train)))
## [1] 16

Melakukan prediksi dengan K-NN menggunakan k = 17 karena hasilnya 16 tdk bagus digunakan (genap)

pred_knn <- class::knn(train = knn_train,
                       test = knn_test, 
                       cl = knn_train_label, 
                       k = 17)

3.2.1 Confsion Matrix

pred_knn_conf <- confusionMatrix(data = as.factor(pred_knn), 
                                 reference = as.factor(knn_test_label),
                                 positive = "1")
pred_knn_conf
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  0  1
##          0 26  5
##          1  5 25
##                                           
##                Accuracy : 0.8361          
##                  95% CI : (0.7191, 0.9185)
##     No Information Rate : 0.5082          
##     P-Value [Acc > NIR] : 9.418e-08       
##                                           
##                   Kappa : 0.672           
##                                           
##  Mcnemar's Test P-Value : 1               
##                                           
##             Sensitivity : 0.8333          
##             Specificity : 0.8387          
##          Pos Pred Value : 0.8333          
##          Neg Pred Value : 0.8387          
##              Prevalence : 0.4918          
##          Detection Rate : 0.4098          
##    Detection Prevalence : 0.4918          
##       Balanced Accuracy : 0.8360          
##                                           
##        'Positive' Class : 1               
## 

Summary Evaluation Logistic Regression =

-Accuracy : 0.8361 –> 83% seberapa mampu model menebak dengan benar target (Health & Not Health).

-Sensitivity (Recall) : 0.8333 –> 83% dari semua data aktual yang positif, seberapa mampu proporsi model saya menebak benar.

-Specificity : 0.8387 –> 83% dari semua data aktual yang negatif, seberapa mampu proporsi model saya menebak yang benar.

-Pos Pred (Precision) : 0.8333 –> 83% dari semua hasil prediksi, seberapa mampu model saya dapat menebak benar kelas positif.

4 Model Evaluation Logistic Regression and K-NN

Model <- c("Logistic Regression", "K-Nearest Neighbor")
Accuracy <- c(0.7705,0.8361)
Recall <- c(0.8000,0.8333)
Specificity <- c(0.7419,0.8387)
Precision <- c(0.7500 ,0.8333)
tabelmodel <- data.frame(Model,Accuracy,Recall,Specificity,Precision)
tabelmodel

5 Kesimpulan

Hasil dari Kedua model cukup baik dalam hal Accuracy, Recall, Specificity dan Precision.

Tetapi pada kasus ini, saya memilih pasien Not Health atau penderita penyakit Jantung, maka dari itu saya memilih model dengan tingkat (Precision) dan (Accuracy) tinggi, karena lebih baik model tersebut dapat memprediksi secara akurat dan tidak salah dalam memprediksi pasien Jantung / FP ( Prediksi benar, tetapi Aktual salah) [Memprediksi orang sakit, sebenarnya tidak sakit], lebih baik dari pada FN ( Prediksi salah tapi Aktual benar) [Memprediksi Tidak sakit, Tetapi sebenarnya Sakit]

Sehingga saya memilih KNN karena memiliki Tinggkat Accuracy dan Precision lebih tinggi dibandingkan Logistic Regression