Objective dan Setup

Penyakit jantung adalah kondisi ketika jantung mengalami gangguan. Bentuk gangguan itu sendiri bisa bermacam-macam. Ada gangguan pada pembuluh darah jantung, irama jantung, katup jantung, atau gangguan akibat bawaan lahir.Untuk mengetahui apakah seorang pasien mengidap penyait jantung biasanya seorang dokter akan melakukan beberapa pemeriksaan kepada pasien, dari hasil pemeriksaan tersebut seorang dokter dapat menentukan apakah seorang pasien sehat atau memiliki penyakit jantung. Kali ini akan dilakukan analisis apakah seseorang mengidap penyakit jantung atau tidak berdasarkan variabel-variabel penunjang. Dengan menggunakan Logistic Regression dan K-Nearest Neighbor sebagai metode untuk melakukan prediksi dengan asumsi yang digunakan adalah seorang dokter menginginkan memprediksi sebanyak mungkin pasien yang terindikasi mengidap penyakit jantung agar dapat dilakukan tindak lanjut sesuai prosedur di rumah sakit tersebut.

Library Required

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(ggplot2)
library(class)
library(tidyr)
library(rsample)
## Warning: package 'rsample' was built under R version 4.0.5
library(caret)
## Warning: package 'caret' was built under R version 4.0.5
## Loading required package: lattice

Overview Data

Data yang akan digunakan pada analisis ini adalah data hasil pemeriksaan pasien apakah memiliki penyakit jantung atau tidak. Jika ada yang tertarik untuk melakukan analisis pada pasien penyakit jantung dapat mengunduh datanya disini.

Data pemeriksaan pasien penyakit jantung tersimpan dalam file heart.csv. Terdiri dari 14 kolom dan 303 baris. Kolom - kolom pada data ini menunjukkan hasil pemeriksaan oleh dokter antara lain:

  • ï..age : umur pasien
  • 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

Proses Prediksi

Pra-proses Data

data <- read.csv("heart.csv")
head(data)

Struktur data dapat digunakan untuk melihat apakah data sudah dalam tipe yang benar atau belum. dengan menggunakan gilmpse() dapat dilihat struktur data yang akan diolah.

glimpse(data)
## 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~

Karena terdapat beberapa data yang masih belum tepat tipe datanya, maka dilakukan data wrangling untuk mengganti tipe data ke tipe data yang sesuai.

data <- data %>% 
  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("No", "Yes")),
         target = factor(target, levels = c(0,1), 
                        labels = c("Health", "Not Health")))

Pastikan kembali apakah data telah memiliki tipe data yang sesuai dengan menggunakan str()

str(data)
## 'data.frame':    303 obs. of  14 variables:
##  $ ï..age  : int  63 37 41 56 57 57 56 44 52 57 ...
##  $ sex     : Factor w/ 2 levels "Female","Male": 2 2 1 2 1 2 1 2 2 2 ...
##  $ 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     : Factor w/ 2 levels "False","True": 2 1 1 1 1 1 1 1 2 1 ...
##  $ 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   : Factor w/ 2 levels "No","Yes": 1 1 1 1 2 1 1 1 1 1 ...
##  $ 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  : Factor w/ 2 levels "Health","Not Health": 2 2 2 2 2 2 2 2 2 2 ...

Jika tipe data sudah tepat maka akan dilihat apakah di dalam terdapat missing value, sebab jika data mengandung missing value nantinya akan menyulitkan untuk dilakukan analisis dan bisa saja analisis yang dibuat tidak akurat.

anyNA(data)
## [1] FALSE

Data yang kita pakai tidak mengandung missing value, namun perlu dicek juga apakah data yang digunakan sudah seimbang atau belum. Karena jika data tidak seimbang atau lebih condong ke salah satu kelas saja maka prediksi yang dihasilkan tidak akurat.

prop.table(table(data$target))
## 
##     Health Not Health 
##  0.4554455  0.5445545
table(data$target)
## 
##     Health Not Health 
##        138        165

Dari pemeriksaan keseimbangan data dapat diasumsikan bahwa data masih dalam kategori seimbang, jadi tidak perlu dilakukan proses lanjutan untuk menyeimbangkan data misal upsampling atau downsampling.

Splitting Data

Data akan dipecah menjadi data_train dan data_test dengan proporsi masing-masing adalah 70% dan 30% Data hasil splitting ini yang nantinya akan digunakan untuk prediksi baik menggunakan Logistic Regression atau KNN

set.seed(789)
index <- initial_split(data = data, prop = 0.7, strata = "target")
data_train <- training(index)
data_test <- testing(index)

Logistic Regression Model

# membuat model dengan menggunakan step wise
model_all <- glm(formula = target ~., data = data_train, family = "binomial")
model_step <- step(object = model_all, direction = "backward", trace = 0)
summary(model_step)
## 
## Call:
## glm(formula = target ~ ï..age + sex + cp + exang + oldpeak + 
##     slope + ca + thal, family = "binomial", data = data_train)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -2.3174  -0.4935   0.1887   0.5595   2.5109  
## 
## Coefficients:
##             Estimate Std. Error z value Pr(>|z|)    
## (Intercept)  5.02993    1.85531   2.711 0.006706 ** 
## ï..age      -0.04866    0.02565  -1.897 0.057843 .  
## sexMale     -1.35643    0.50998  -2.660 0.007819 ** 
## cp           0.89627    0.21725   4.125  3.7e-05 ***
## exangYes    -1.15813    0.47232  -2.452 0.014206 *  
## oldpeak     -0.51477    0.25227  -2.041 0.041298 *  
## slope        1.02853    0.43737   2.352 0.018693 *  
## ca          -0.74361    0.22558  -3.296 0.000979 ***
## thal        -0.89734    0.31583  -2.841 0.004494 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 293.58  on 212  degrees of freedom
## Residual deviance: 152.81  on 204  degrees of freedom
## AIC: 170.81
## 
## Number of Fisher Scoring iterations: 5

Dari proses diatas, terlihat terdapat variabel yang tidak signifikan, maka akan dicoba untuk menghilangkan variabel yang tidak signifikan.

wise_model <- glm(formula = target ~ sex + cp + exang + oldpeak + 
    slope + ca + thal, family = "binomial", data = data_train)
summary(wise_model)
## 
## Call:
## glm(formula = target ~ sex + cp + exang + oldpeak + slope + ca + 
##     thal, family = "binomial", data = data_train)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -2.5425  -0.4633   0.2021   0.5772   2.5205  
## 
## Coefficients:
##             Estimate Std. Error z value Pr(>|z|)    
## (Intercept)   2.1807     1.0135   2.152 0.031433 *  
## sexMale      -1.2278     0.5041  -2.435 0.014875 *  
## cp            0.9045     0.2179   4.151  3.3e-05 ***
## exangYes     -1.1007     0.4616  -2.384 0.017103 *  
## oldpeak      -0.5527     0.2551  -2.167 0.030243 *  
## slope         1.0888     0.4365   2.494 0.012626 *  
## ca           -0.8093     0.2158  -3.751 0.000176 ***
## thal         -0.8738     0.3139  -2.784 0.005373 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 293.58  on 212  degrees of freedom
## Residual deviance: 156.53  on 205  degrees of freedom
## AIC: 172.53
## 
## Number of Fisher Scoring iterations: 5

Prediksi Logistic Regression

Melakukan prediksi menggunakan data test dengan model wise_model

data_test$heart_predict <- predict(object = wise_model, newdata = data_test, type = "response")
head(data_test$heart_predict)
## [1] 0.8207188 0.2848852 0.6725081 0.9867313 0.9881034 0.6350015

Selanjutnya akan divisualisasikan distribusi probabilitas dari hasil prediksi, apakah banyak diprediksi ke kelas positif atau negatif.

ggplot(data_test, aes(x=heart_predict)) +
  geom_density(lwd=0.5) +
  labs(title = "Distribution of Probability Prediction Data") +
  theme_minimal()

Dari hasil visualisasi diperoleh bahwa model dapat memprediksi banyak pada kelas positif yaitu pasien mengidap penyakit jatung

Evaluasi Model Logistic Regression

data_test$heart_predict <- factor(ifelse(data_test$heart_predict >0.5, "Not Health", "Health"))
logistic_confusion <- confusionMatrix(data = data_test$heart_predict, reference = data_test$target, positive = "Not Health")
logistic_confusion
## Confusion Matrix and Statistics
## 
##             Reference
## Prediction   Health Not Health
##   Health         29          6
##   Not Health     12         43
##                                           
##                Accuracy : 0.8             
##                  95% CI : (0.7025, 0.8769)
##     No Information Rate : 0.5444          
##     P-Value [Acc > NIR] : 3.697e-07       
##                                           
##                   Kappa : 0.5919          
##                                           
##  Mcnemar's Test P-Value : 0.2386          
##                                           
##             Sensitivity : 0.8776          
##             Specificity : 0.7073          
##          Pos Pred Value : 0.7818          
##          Neg Pred Value : 0.8286          
##              Prevalence : 0.5444          
##          Detection Rate : 0.4778          
##    Detection Prevalence : 0.6111          
##       Balanced Accuracy : 0.7924          
##                                           
##        'Positive' Class : Not Health      
## 

Interpretasi model Logistic regression

exp(wise_model$coefficients) %>% 
  data.frame()

Interpretasi : kejadian sesorang yang berjenis kelamin laki - laki yang terkena penyakit jantung lebih tidak mungkin dibandingkan perempuan.

K-Nearest Neighbor

Terlebih dahulu akan dicek summary dari data_train karena melalui ringkasan ini dapat diketahui summary statistik dari setiap kolom dan juga dapat melihat apakah antar kolom memiliki skala yang sama atau tidak, karena jika setiap kolom memiliki skala yang berbeda beda maka akan mempengaruhi hasil prediksi sehingga menjadi tidak akurat.

summary(data_train)
##      ï..age          sex            cp            trestbps          chol      
##  Min.   :29.00   Female: 66   Min.   :0.0000   Min.   : 94.0   Min.   :149.0  
##  1st Qu.:48.00   Male  :147   1st Qu.:0.0000   1st Qu.:120.0   1st Qu.:211.0  
##  Median :55.00                Median :1.0000   Median :130.0   Median :236.0  
##  Mean   :54.51                Mean   :0.9437   Mean   :132.1   Mean   :246.7  
##  3rd Qu.:61.00                3rd Qu.:2.0000   3rd Qu.:140.0   3rd Qu.:274.0  
##  Max.   :77.00                Max.   :3.0000   Max.   :192.0   Max.   :564.0  
##     fbs         restecg          thalach      exang        oldpeak     
##  False:184   Min.   :0.0000   Min.   : 71.0   No :141   Min.   :0.000  
##  True : 29   1st Qu.:0.0000   1st Qu.:136.0   Yes: 72   1st Qu.:0.000  
##              Median :0.0000   Median :154.0             Median :0.800  
##              Mean   :0.4695   Mean   :149.8             Mean   :1.061  
##              3rd Qu.:1.0000   3rd Qu.:167.0             3rd Qu.:1.800  
##              Max.   :2.0000   Max.   :202.0             Max.   :6.200  
##      slope             ca              thal              target   
##  Min.   :0.000   Min.   :0.0000   Min.   :0.000   Health    : 97  
##  1st Qu.:1.000   1st Qu.:0.0000   1st Qu.:2.000   Not Health:116  
##  Median :1.000   Median :0.0000   Median :2.000                   
##  Mean   :1.394   Mean   :0.6995   Mean   :2.315                   
##  3rd Qu.:2.000   3rd Qu.:1.0000   3rd Qu.:3.000                   
##  Max.   :2.000   Max.   :4.0000   Max.   :3.000

Scalling Data

Melakukan proses scaling untuk menyamakan skala dari setiap kolomnya.

data_train_x <- data_train[,-14]
data_test_x <- data_test[,-c(14,15)]

data_train_y <- data_train[,14]
data_test_y <- data_test[,14]

Karena terdapat kolom yang masih dalam bentuk factor maka akan diubah kedalam bentuk numerik.

data_train_x$sex <- ifelse(data_train_x$sex == "Female",0,1)
data_train_x$fbs <- ifelse(data_train_x$fbs == "False",0,1)
data_train_x$exang <- ifelse(data_train_x$exang == "No",0,1)
data_test_x$sex <- ifelse(data_test_x$sex == "Female",0,1)
data_test_x$fbs <- ifelse(data_test_x$fbs == "False",0,1)
data_test_x$exang <- ifelse(data_test_x$exang == "No",0,1)

Scaling pada variabel prediktor

train_scale_x <- scale(x = data_train_x)
test_scale_x <- scale(x = data_test_x,
                      center = attr(train_scale_x, "scaled:center"),
                      scale = attr(train_scale_x, "scaled:scale"))

Menentukan k optimum

Setelah data sudah dilakukan scaling, selanjutnya menentukan k optimum untuk KNN. k optimum biasanya ditentukan dari akar jumlah data yang akan diolah, dikarenakan target pada data kita adalah genap maka k optimum sebaiknya ganjil.

round(sqrt(nrow(data_train)))
## [1] 15

Prediksi K-Nearest Neighbor

Melakukan prediksi pada data test menggunakan algoritma KNN.

pred_heart <- knn(train = train_scale_x,
                  test = test_scale_x,
                  cl = data_train_y,
                  k = 15)
head(pred_heart)
## [1] Not Health Not Health Not Health Not Health Not Health Not Health
## Levels: Health Not Health

Evaluasi Model KNN

confusionMatrix(data = pred_heart, reference = data_test_y, positive = "Not Health")
## Confusion Matrix and Statistics
## 
##             Reference
## Prediction   Health Not Health
##   Health         27          1
##   Not Health     14         48
##                                         
##                Accuracy : 0.8333        
##                  95% CI : (0.74, 0.9036)
##     No Information Rate : 0.5444        
##     P-Value [Acc > NIR] : 7.067e-09     
##                                         
##                   Kappa : 0.6551        
##                                         
##  Mcnemar's Test P-Value : 0.001946      
##                                         
##             Sensitivity : 0.9796        
##             Specificity : 0.6585        
##          Pos Pred Value : 0.7742        
##          Neg Pred Value : 0.9643        
##              Prevalence : 0.5444        
##          Detection Rate : 0.5333        
##    Detection Prevalence : 0.6889        
##       Balanced Accuracy : 0.8191        
##                                         
##        'Positive' Class : Not Health    
## 

Perbandingan Kedua Model

Perlu diingat kembali bahwa pada kasus ini akan digunakan asumsi bahwa seorang dokter akan memprediksi sebanyak banyaknya pasien yang mengidap penyakit jantung. Artinya dokter akan memasukkan pasien yang mungkin hanya memiliki persentase peluang mengidap penyakit jantung yang tergolong kecil kedalam kategori mengidap penyakit jantung (Not Health). Sang dokter memilih asumsi ini dikarenakan jika banyak yang diprediksi kedalam kategori Not Health dan benar pasien tersebut sakit jantung maka dokter akan melakukan tindak lanjut. Namun jika hasil prediksi tersebut pasien dianggap sehat namun kenyataanya sakit jantung, maka hal tersebut akan berbahaya bagi pasien. Jadi, sang dokter menginginkan hal tersebut diminimalisir. Dalam perhitungannya, jika dokter menginginkan asumsi seperti diatas, maka dokter harus melihat metrik sensitivity untuk meminimalisir hasil prediksi salah namun pada kenyataanya benar.

Dari tabel yang disajikan diatas, dapat dilihat bahwa kemampuan model memprediksi benar dari data aktual pasien yang sakit Not Health lebih baik menggunakan model K-Nearest Neighbor yang menghasilkan sensitivity sebesar 97% dibandingkan dengan Logistic Regression.

Kesimpulan

Dengan menggunakan data heart.csv, prediksi pasien mengidap penyakit jantung atau tidak menggunakan Logistic Regression dan KNN sudah menghasilkan prediksi yang cukup bagus, dalam hal ini dengan menggunakan asumsi untuk meminimalisir prediksi yang salah pada pasien yang ternyata mengidap penyakit jantung maka dilihat metrik sensitivity dimana dari hasil perbandingan diperoleh bahwa KNN adalah metode yang cocok digunakan untuk prediksi data heart.csv karena menghasilkan 97% sensitivitas hasil prediksi

*)Asumsi yang digunakan tidak harus meminimalisir FN (False Negative)

Data Hasil prediksi Logistik

Data Hasil prediksi KNN