# clear-up the environment
rm(list = ls())

# chunk options
knitr::opts_chunk$set(
  message = FALSE,
  warning = FALSE,
  fig.align = "center",
  comment = "#>"
)

options(scipen = 123)

1 Intro

Salam bahagia kepada para pembaca yang membaca report ini. Dalam report ini saya akan membuat model Klasifikasi logistic regression dan model KNN untuk memprediksi apakah seorang pasien memiliki masalah jantung atau tidak. dimana model Logistic regression dan KNN adalah model supervised learning untuk klasifikasi.

2 Library and Set up

kita akan memasukan library packages yang akan kita gunakan dalam LBB ini.

library(dplyr)
library(class)
library(tidyr)
library(gmodels)
library(gtools)
library(caret)

3 Read and Check data Structure

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~

Informasi penting dalam data :

  1. ï..age : dalam beberapa tahun
  2. sex : (1 = laki-laki; 0 = perempuan)
  3. cp : tipe nyeri yang paling parah
  4. trestbps : melacak tekanan darah(dalam mm Hg saat masuk ke rumah sakit)
  5. chol : kolestoral dalam mg / dl
  6. fbs : (gula darah puasa> 120 mg / dl) (1 = benar; 0 = salah)
  7. restecg : mengembalikan hasil elektrokardiografi
  8. thalach : denyut jantung maksimum tercapai
  9. exang : exercise induced angina (1 = ya; 0 = tidak)
  10. oldpeak : ST depresi yang disebabkan oleh olahraga relatif terhadap istirahat
  11. slope : kemiringan segmen ST latihan puncak
  12. ca : jumlah pembuluh darah utama (0-3) diwarnai dengan fluoroskopi
  13. thal : 3 = normal; 6 = cacat tetap; 7 = cacat yang dapat dibalik
  14. target : 1 = sakit atau 0 = tidak sakit

4 Data cleansing and preperation

Disini kita akan mengubah tipe data yang tidak sesuai dan memeriksa apakah ada missing value dalam dataset.

  1. kategori Sex dari 0/1 menjadi Female/Male
  2. FBS dari 0/1 menjadi False/True
  3. exang dari 0/1 menjadi No/Yes
  4. Target dari 1/0 menjadi Sick/Healthy
  5. cp menjadi tipe kategori
  6. fbs menjadi tipe kategori
  7. restecg menjadi tipe kategori
  8. slope menjadi tipe kategori
  9. ca menjadi tipe kategori
  10. thal menjadi tipe kategori
heart_new <- heart %>%
  mutate(sex = as.character(sex),
         sex = ifelse(sex == "0","Female", "Male"),
         sex = as.factor(sex),
         fbs = as.character(fbs),
         fbs = ifelse(fbs == "0","False", "True"),
         fbs = as.factor(fbs),
         exang = as.character(exang),
         exang = ifelse(exang == "0","No", "Yes"),
         exang = as.factor(exang),
         target = as.character(target),
         target = ifelse(target == "0","Health", "Sick"),
         target = as.factor(target)) %>%
  mutate_at(.vars = c("cp", "fbs", "restecg", "slope", "ca", "thal"), .funs = as.factor)

glimpse(heart_new)
#> Rows: 303
#> Columns: 14
#> $ ï..age   <int> 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 <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      <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  <int> 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> Sick, Sick, Sick, Sick, Sick, Sick, Sick, Sick, Sick, Sick, S~

sekarang kita akan memeriksa apakah adanya missing value dalam data

anyNA(heart_new)
#> [1] FALSE
colSums(is.na(heart_new))
#>   ï..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

dapat dilihat kalau data yang diberikan sudahlah bersih tanpa adanya missing value sehingga data sudah bisa langsung dibuat model tanpa cleansing.

5 Data Propotion

sebelum membuat model, kita ingin terlebih dahulu melihat apakah proposi data yang ada sudah seimbang atau tidak.

prop.table(table(heart_new$target))
#> 
#>    Health      Sick 
#> 0.4554455 0.5445545

proposi yang ada masih bisa dikategorikan sebagai seimbang. sehinga kita tidak perlu untuk melakukan penyesuaian jumlah data.

6 Data train dan Data test

Disini kita akan memecah jumlah data menjadi 2 bagian yaitu data train untuk melatih model dan data test untuk melakukan prediksi. pemecahan dilakukan dengan jumlah 70% data train dan 30% data test

RNGkind(sample.kind = "Rounding")
set.seed(417)
idx <- sample(nrow(heart_new), nrow(heart_new)*0.7)
heart_train <- heart_new[idx,]
heart_test <- heart_new[-idx,]
prop.table(table(heart_train$target))
#> 
#>    Health      Sick 
#> 0.4528302 0.5471698
prop.table(table(heart_test$target))
#> 
#>    Health      Sick 
#> 0.4615385 0.5384615

proposi data train dan data test sudah seimbang. sekarang kita akan mulai membuat model.

7 Logistic Regression

Kita akan mulai dengan membuat model logistic regression dengan function glm(). target = target, prediktor akan kita pilih yang kita anggap mempengaruhi apakah prediktor tersebut mempengaruhi seseorang terkena penyakit jantung.

model_heart <- glm(target ~ ï..age + sex + trestbps + chol + fbs + thalach + exang + oldpeak + ca, data = heart_train, family = "binomial")

summary(model_heart)
#> 
#> Call:
#> glm(formula = target ~ ï..age + sex + trestbps + chol + fbs + 
#>     thalach + exang + oldpeak + ca, family = "binomial", data = heart_train)
#> 
#> Deviance Residuals: 
#>     Min       1Q   Median       3Q      Max  
#> -2.6461  -0.3579   0.1577   0.4897   2.0338  
#> 
#> Coefficients:
#>               Estimate Std. Error z value   Pr(>|z|)    
#> (Intercept)    1.52499    2.98496   0.511   0.609429    
#> ï..age         0.02772    0.03015   0.919   0.357908    
#> sexMale       -1.87723    0.54831  -3.424   0.000618 ***
#> trestbps      -0.01002    0.01321  -0.758   0.448267    
#> chol          -0.01353    0.00548  -2.469   0.013546 *  
#> fbsTrue        0.81222    0.60685   1.338   0.180758    
#> thalach        0.03563    0.01405   2.536   0.011213 *  
#> exangYes      -2.22027    0.53489  -4.151 0.00003312 ***
#> oldpeak       -0.66531    0.24109  -2.760   0.005787 ** 
#> ca1           -2.62533    0.57431  -4.571 0.00000485 ***
#> ca2           -3.16290    0.77351  -4.089 0.00004332 ***
#> ca3           -2.40633    0.91657  -2.625   0.008656 ** 
#> ca4           13.19262 1005.27414   0.013   0.989529    
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> (Dispersion parameter for binomial family taken to be 1)
#> 
#>     Null deviance: 292.00  on 211  degrees of freedom
#> Residual deviance: 140.55  on 199  degrees of freedom
#> AIC: 166.55
#> 
#> Number of Fisher Scoring iterations: 14

dilihat dari summary model yang sudah kita buat. masih ada beberapa prediktor yang tidak memiliki korelasi yang kuat. oleh karena itu, kita akan melakukan fitting model stepwise backward.

model_heart_back <- step(model_heart, direction = "backward", trace = F)

summary(model_heart_back)
#> 
#> Call:
#> glm(formula = target ~ sex + chol + thalach + exang + oldpeak + 
#>     ca, family = "binomial", data = heart_train)
#> 
#> Deviance Residuals: 
#>     Min       1Q   Median       3Q      Max  
#> -2.5652  -0.3784   0.1573   0.4906   2.2048  
#> 
#> Coefficients:
#>               Estimate Std. Error z value  Pr(>|z|)    
#> (Intercept)   2.150905   2.253729   0.954  0.339893    
#> sexMale      -1.746675   0.518088  -3.371  0.000748 ***
#> chol         -0.012026   0.005157  -2.332  0.019701 *  
#> thalach       0.029999   0.012819   2.340  0.019278 *  
#> exangYes     -2.236655   0.525195  -4.259 0.0000206 ***
#> oldpeak      -0.659266   0.229736  -2.870  0.004109 ** 
#> ca1          -2.515567   0.561283  -4.482 0.0000074 ***
#> ca2          -2.832273   0.716643  -3.952 0.0000775 ***
#> ca3          -2.087391   0.855958  -2.439  0.014742 *  
#> ca4          13.140749 984.179544   0.013  0.989347    
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> (Dispersion parameter for binomial family taken to be 1)
#> 
#>     Null deviance: 292.00  on 211  degrees of freedom
#> Residual deviance: 143.45  on 202  degrees of freedom
#> AIC: 163.45
#> 
#> Number of Fisher Scoring iterations: 14

setelah fitting kita mendapatkan AIC yang lebih kecil dan kita juga melihat kalau semua prediktor sudah memiliki signifikasi yang kuat dengan target.

8 Prediction Logistic Regression

kita sudah membuat sebuah model_heart_backwards yang akan kita gunakan untuk melakukan prediksi kepada data test yang sudah kita pisah diawal.

heart_test$pred.Risk <- predict(model_heart_back, heart_test, type = "response")

heart_test$pred_heart <-factor(ifelse(heart_test$pred.Risk > 0.5, "Sick","Health"))

heart_test[1:10, c("pred_heart", "target")]

setelah membuat model, kita ingin mengevaluasi model yang sudah kita buat

confusionMatrix(heart_test$pred_heart, heart_test$target, positive = "Sick")
#> Confusion Matrix and Statistics
#> 
#>           Reference
#> Prediction Health Sick
#>     Health     33   13
#>     Sick        9   36
#>                                           
#>                Accuracy : 0.7582          
#>                  95% CI : (0.6572, 0.8419)
#>     No Information Rate : 0.5385          
#>     P-Value [Acc > NIR] : 0.00001259      
#>                                           
#>                   Kappa : 0.5169          
#>                                           
#>  Mcnemar's Test P-Value : 0.5224          
#>                                           
#>             Sensitivity : 0.7347          
#>             Specificity : 0.7857          
#>          Pos Pred Value : 0.8000          
#>          Neg Pred Value : 0.7174          
#>              Prevalence : 0.5385          
#>          Detection Rate : 0.3956          
#>    Detection Prevalence : 0.4945          
#>       Balanced Accuracy : 0.7602          
#>                                           
#>        'Positive' Class : Sick            
#> 

Metrics yang akan digunakan adalah Precision dengan alasan kalau dalam kasus prediksi masalah jantung. kita tidak bisa mengambil resiko sehingga kita membutuhkan akurasi yang sangat tepat agar tidak terjadi kesalahan dimana model kita mendapatkan hasil precision sebesar 80%

9 KNN model

kita sudah membuat model untuk Logistic Regression. sekarang kita akan membuat model KNN dan membandingkan kedua model.

heart2 <- heart %>%
  mutate(target = as.character(target),
         target = ifelse(target == "0","Health", "Sick"),
         target = as.factor(target))
RNGkind(sample.kind = "Rounding")
set.seed(417)
idx2 <- sample(nrow(heart2), nrow(heart2)*0.7)
heart2_train <- heart2[idx2,] #70% data train
heart2_test <- heart2[-idx2,] #30% data test

Karena tiap prediktor memiliki range yang berbeda maka kita perlu melakukan scaling agar range tiap prediktor tidak berbeda jauh

heart_train_scale <- heart2_train %>%
  select_if(is.numeric) %>%
  scale()

heart_test_scale <- heart2_test %>%
  select_if(is.numeric) %>%
  scale(center = attr(heart_train_scale, "scaled:center"),
        scale = attr(heart_train_scale, "scaled:scale"))

heart_train_target <- heart2_train$target

heart_test_target <- heart2_test$target

disini kita akan melihat nilai K untuk permodelan.

k_heart <- sqrt(nrow(heart_train_scale))

k_heart
#> [1] 14.56022

selanjutnya kita membuat model KNN.

knn_heart <- knn(train = heart_train_scale, test = heart_test_scale, cl= heart_train_target, k=14)

selanjutnya kita akan melakukan evaluasi model KNN yang sudah kita bentuk

confusionMatrix(data= knn_heart, reference = heart_test_target, positive = "Sick")
#> Confusion Matrix and Statistics
#> 
#>           Reference
#> Prediction Health Sick
#>     Health     32    5
#>     Sick       10   44
#>                                           
#>                Accuracy : 0.8352          
#>                  95% CI : (0.7427, 0.9047)
#>     No Information Rate : 0.5385          
#>     P-Value [Acc > NIR] : 0.000000002394  
#>                                           
#>                   Kappa : 0.6655          
#>                                           
#>  Mcnemar's Test P-Value : 0.3017          
#>                                           
#>             Sensitivity : 0.8980          
#>             Specificity : 0.7619          
#>          Pos Pred Value : 0.8148          
#>          Neg Pred Value : 0.8649          
#>              Prevalence : 0.5385          
#>          Detection Rate : 0.4835          
#>    Detection Prevalence : 0.5934          
#>       Balanced Accuracy : 0.8299          
#>                                           
#>        'Positive' Class : Sick            
#> 

dengan model KNN kita mendapatkan nilai precision sebesar 81.4%

10 Conclusion

dengan evalusi model yang sudah kita lakukan. kita mendapatkan hasil nilai precision logistic regression sebesar 80% dan model KNN sebesar 81.4%. model KNN mendapatkan hasil presisi yang lebih tinggi daripada model logistic regression.

jika saya seorang dokter. maka saya akan memakai model KNN untuk denga metric precision yang lebih tinggi. karena menghadapi penyakit jantung yang bersifat fatal, saya tidak ingin salah memprediksi agar saya tidak salah dalam menangani pasien.