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(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
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:
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.
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)
# 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
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
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
##
exp(wise_model$coefficients) %>%
data.frame()
Interpretasi : kejadian sesorang yang berjenis kelamin laki - laki yang terkena penyakit jantung lebih tidak mungkin dibandingkan perempuan.
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
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"))
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
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
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
##
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.
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)