WHO memperkirakan 12 juta kematian terjadi di seluruh dunia, setiap tahun karena penyakit jantung. Separuh kematian di Amerika Serikat dan negara maju lainnya disebabkan oleh penyakit kardiovaskular. Prognosis dini penyakit kardiovaskular dapat membantu dalam membuat keputusan tentang perubahan gaya hidup pada pasien berisiko tinggi dan pada gilirannya mengurangi komplikasi. Penelitian ini bermaksud untuk menentukan faktor risiko penyakit jantung yang paling relevan serta memprediksi risiko keseluruhan menggunakan logistic regression.
Dataset berikut berasal dari situs web Kaggle, dan berasal dari studi kardiovaskular yang sedang berlangsung pada penduduk kota Framingham, Massachusetts. Tujuan klasifikasi adalah untuk memprediksi apakah pasien memiliki risiko penyakit jantung koroner (PJK) 10 tahun di masa depan. Kumpulan data menyediakan informasi pasien. Ini mencakup lebih dari 4.000 records dan 15 variable(kolom).
Library yang digunakan :
library(class)
library(GGally)## Loading required package: ggplot2
## Registered S3 method overwritten by 'GGally':
## method from
## +.gg ggplot2
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(tidyverse)## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ forcats 1.0.0 ✔ stringr 1.5.0
## ✔ lubridate 1.9.2 ✔ tibble 3.1.8
## ✔ purrr 1.0.1 ✔ tidyr 1.3.0
## ✔ readr 2.1.4
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(gtools)pasien_jantung <- read.csv("data/framingham.csv")
glimpse(pasien_jantung)## Rows: 4,238
## Columns: 16
## $ male <int> 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, …
## $ age <int> 39, 46, 48, 61, 46, 43, 63, 45, 52, 43, 50, 43, 46, 41…
## $ education <int> 4, 2, 1, 3, 3, 2, 1, 2, 1, 1, 1, 2, 1, 3, 2, 2, 3, 2, …
## $ currentSmoker <int> 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, …
## $ cigsPerDay <int> 0, 0, 20, 30, 23, 0, 0, 20, 0, 30, 0, 0, 15, 0, 9, 20,…
## $ BPMeds <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, …
## $ prevalentStroke <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ prevalentHyp <int> 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, …
## $ diabetes <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ totChol <int> 195, 250, 245, 225, 285, 228, 205, 313, 260, 225, 254,…
## $ sysBP <dbl> 106.0, 121.0, 127.5, 150.0, 130.0, 180.0, 138.0, 100.0…
## $ diaBP <dbl> 70.0, 81.0, 80.0, 95.0, 84.0, 110.0, 71.0, 71.0, 89.0,…
## $ BMI <dbl> 26.97, 28.73, 25.34, 28.58, 23.10, 30.30, 33.11, 21.68…
## $ heartRate <int> 80, 95, 75, 65, 85, 77, 60, 79, 76, 93, 75, 72, 98, 65…
## $ glucose <int> 77, 76, 70, 103, 85, 99, 85, 78, 79, 88, 76, 61, 64, 8…
## $ TenYearCHD <int> 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, …
anyNA(pasien_jantung)## [1] TRUE
missing_values <- colSums(is.na(pasien_jantung))
print(missing_values)## male age education currentSmoker cigsPerDay
## 0 0 105 0 29
## BPMeds prevalentStroke prevalentHyp diabetes totChol
## 53 0 0 0 50
## sysBP diaBP BMI heartRate glucose
## 0 0 19 1 388
## TenYearCHD
## 0
pasien_jantung <- na.omit(pasien_jantung)
anyNA(pasien_jantung)## [1] FALSE
pasien_jantung <- pasien_jantung %>%
mutate(male = as.factor(male),
currentSmoker = as.factor(currentSmoker),
BPMeds = as.factor(BPMeds),
prevalentStroke = as.factor(prevalentStroke),
prevalentHyp = as.factor(male),
diabetes = as.factor(diabetes),
TenYearCHD = as.factor(TenYearCHD))
str(pasien_jantung)## 'data.frame': 3656 obs. of 16 variables:
## $ male : Factor w/ 2 levels "0","1": 2 1 2 1 1 1 1 1 2 2 ...
## $ age : int 39 46 48 61 46 43 63 45 52 43 ...
## $ education : int 4 2 1 3 3 2 1 2 1 1 ...
## $ currentSmoker : Factor w/ 2 levels "0","1": 1 1 2 2 2 1 1 2 1 2 ...
## $ cigsPerDay : int 0 0 20 30 23 0 0 20 0 30 ...
## $ BPMeds : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
## $ prevalentStroke: Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
## $ prevalentHyp : Factor w/ 2 levels "0","1": 2 1 2 1 1 1 1 1 2 2 ...
## $ diabetes : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
## $ totChol : int 195 250 245 225 285 228 205 313 260 225 ...
## $ sysBP : num 106 121 128 150 130 ...
## $ diaBP : num 70 81 80 95 84 110 71 71 89 107 ...
## $ BMI : num 27 28.7 25.3 28.6 23.1 ...
## $ heartRate : int 80 95 75 65 85 77 60 79 76 93 ...
## $ glucose : int 77 76 70 103 85 99 85 78 79 88 ...
## $ TenYearCHD : Factor w/ 2 levels "0","1": 1 1 1 2 1 1 2 1 1 1 ...
## - attr(*, "na.action")= 'omit' Named int [1:582] 15 22 27 34 37 43 50 55 71 73 ...
## ..- attr(*, "names")= chr [1:582] "15" "22" "27" "34" ...
Berikut adalah keterangan untuk setiap kolom : - male:
Gender
- age: Usia
- education: Jenjang Pendidikan
- currentSmoker: Status Merokok
- cigsPerDay: Konsumsi Rokok Per Hari
- BPMeds: Status Tekanan Darah
- prevalentStroke: Status Stroke
- prevalentHyp: Status Hipertensi
- diabetes : Status Diabetes
- totChol: Total Kolesterol
- sysBP: Tekanan Darah Systolic
- diaBP: Tekanan Darah Diastolic
- BMI: Body Mass Index
- heartRate: Heart Rate
- glucose: Level Glukosa
- TenYearCHD: Resiko Penyakit Jantung dalam 10 Tahun Ke
Depan
summary(pasien_jantung)## male age education currentSmoker cigsPerDay
## 0:2034 Min. :32.00 Min. :1.00 0:1868 Min. : 0.000
## 1:1622 1st Qu.:42.00 1st Qu.:1.00 1:1788 1st Qu.: 0.000
## Median :49.00 Median :2.00 Median : 0.000
## Mean :49.56 Mean :1.98 Mean : 9.022
## 3rd Qu.:56.00 3rd Qu.:3.00 3rd Qu.:20.000
## Max. :70.00 Max. :4.00 Max. :70.000
## BPMeds prevalentStroke prevalentHyp diabetes totChol sysBP
## 0:3545 0:3635 0:2034 0:3557 Min. :113.0 Min. : 83.5
## 1: 111 1: 21 1:1622 1: 99 1st Qu.:206.0 1st Qu.:117.0
## Median :234.0 Median :128.0
## Mean :236.9 Mean :132.4
## 3rd Qu.:263.2 3rd Qu.:144.0
## Max. :600.0 Max. :295.0
## diaBP BMI heartRate glucose TenYearCHD
## Min. : 48.00 Min. :15.54 Min. : 44.00 Min. : 40.00 0:3099
## 1st Qu.: 75.00 1st Qu.:23.08 1st Qu.: 68.00 1st Qu.: 71.00 1: 557
## Median : 82.00 Median :25.38 Median : 75.00 Median : 78.00
## Mean : 82.91 Mean :25.78 Mean : 75.73 Mean : 81.86
## 3rd Qu.: 90.00 3rd Qu.:28.04 3rd Qu.: 82.00 3rd Qu.: 87.00
## Max. :142.50 Max. :56.80 Max. :143.00 Max. :394.00
prop.table(table(pasien_jantung$TenYearCHD))##
## 0 1
## 0.8476477 0.1523523
pasien_tidak_jantung <- pasien_jantung %>%
filter(pasien_jantung$TenYearCHD %in% "0")
pasien_tidak_jantung_downsample <- pasien_tidak_jantung[sample(nrow(pasien_tidak_jantung), 557), ]
pasien_jantung <- pasien_jantung %>%
filter(pasien_jantung$TenYearCHD %in% "1")
pasien_jantung <- bind_rows(pasien_tidak_jantung_downsample,pasien_jantung)
pasien_jantung <- pasien_jantung[sample(1:nrow(pasien_jantung)), ]prop.table(table(pasien_jantung$TenYearCHD))##
## 0 1
## 0.5 0.5
ggcorr(pasien_jantung, label = T)## Warning in ggcorr(pasien_jantung, label = T): data in column(s) 'male',
## 'currentSmoker', 'BPMeds', 'prevalentStroke', 'prevalentHyp', 'diabetes',
## 'TenYearCHD' are not numeric and were ignored
set.seed(417)
index <- sample(x = nrow(pasien_jantung), size = nrow(pasien_jantung)*0.8)
pasien_jantung_train <- pasien_jantung[index,]
pasien_jantung_test <- pasien_jantung[-index,]# model base
model_pasien_jantung <- glm(
formula = TenYearCHD ~ .,
data = pasien_jantung_train,
family = "binomial",
control = list(trace=FALSE) # cek perubahan deviasi setiap iterasi
)
summary(model_pasien_jantung)##
## Call:
## glm(formula = TenYearCHD ~ ., family = "binomial", data = pasien_jantung_train,
## control = list(trace = FALSE))
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.265 -1.001 0.365 1.001 2.240
##
## Coefficients: (1 not defined because of singularities)
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -7.041253 0.991289 -7.103 1.22e-12 ***
## male1 0.531743 0.161034 3.302 0.000960 ***
## age 0.054760 0.010057 5.445 5.19e-08 ***
## education -0.068126 0.074764 -0.911 0.362185
## currentSmoker1 0.320431 0.250059 1.281 0.200045
## cigsPerDay 0.012425 0.010113 1.229 0.219218
## BPMeds1 0.109960 0.412281 0.267 0.789693
## prevalentStroke1 1.227634 1.099875 1.116 0.264354
## prevalentHyp1 NA NA NA NA
## diabetes1 0.818239 0.691344 1.184 0.236592
## totChol 0.003258 0.001666 1.955 0.050533 .
## sysBP 0.022441 0.005788 3.877 0.000106 ***
## diaBP -0.003623 0.009980 -0.363 0.716575
## BMI 0.015231 0.019437 0.784 0.433265
## heartRate -0.006627 0.006613 -1.002 0.316316
## glucose 0.004987 0.004166 1.197 0.231254
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 1234.4 on 890 degrees of freedom
## Residual deviance: 1058.4 on 876 degrees of freedom
## AIC: 1088.4
##
## Number of Fisher Scoring iterations: 4
# stepwise
model_step <- step(object = model_pasien_jantung,
direction = "backward",
trace = F)
summary(model_step)##
## Call:
## glm(formula = TenYearCHD ~ male + age + cigsPerDay + diabetes +
## totChol + sysBP, family = "binomial", data = pasien_jantung_train,
## control = list(trace = FALSE))
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.273 -1.009 0.408 1.005 2.207
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -6.976530 0.685436 -10.178 < 2e-16 ***
## male1 0.516739 0.156168 3.309 0.000937 ***
## age 0.055798 0.009608 5.808 6.34e-09 ***
## cigsPerDay 0.020972 0.006591 3.182 0.001463 **
## diabetes1 1.397930 0.511287 2.734 0.006254 **
## totChol 0.003083 0.001650 1.868 0.061763 .
## sysBP 0.021523 0.003668 5.868 4.40e-09 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 1234.4 on 890 degrees of freedom
## Residual deviance: 1065.7 on 884 degrees of freedom
## AIC: 1079.7
##
## Number of Fisher Scoring iterations: 4
exp(model_step$coefficient)## (Intercept) male1 age cigsPerDay diabetes1 totChol
## 0.0009335367 1.6765521990 1.0573843107 1.0211934388 4.0468141905 1.0030877274
## sysBP
## 1.0217562106
Setiap kenaikan 1 nilai usia, pasien meningkatkan odds berpenyakit jantung sebesar 1.06 kali dengan catatan seluruh prediktor lain bernilai tetap. Setiap pasien laki- laki, 1.7 kali lebih mungkin berpenyakit jantung dibandingkan dengan perempuan.
pasien_jantung_test$pred_Risk <- predict(
object = model_step,
newdata = pasien_jantung_test,
type = "response"
)pasien_jantung_test$pred_Label <-
ifelse(pasien_jantung_test$pred_Risk < 0.5, 0, 1) %>%
as.factor()# lihat hasil prediksi
pasien_jantung_test %>%
select(pred_Risk, pred_Label, TenYearCHD)table(predict = pasien_jantung_test$pred_Label,
actual = pasien_jantung_test$TenYearCHD)## actual
## predict 0 1
## 0 83 30
## 1 42 68
# confusion matrix
library(caret)## Loading required package: lattice
##
## Attaching package: 'caret'
## The following object is masked from 'package:purrr':
##
## lift
confusionMatrix(
data = pasien_jantung_test$pred_Label, # hasil prediksi kita
reference = pasien_jantung_test$TenYearCHD, # data asli/aktual
positive = "1"
)## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 83 30
## 1 42 68
##
## Accuracy : 0.6771
## 95% CI : (0.6115, 0.738)
## No Information Rate : 0.5605
## P-Value [Acc > NIR] : 0.0002452
##
## Kappa : 0.3532
##
## Mcnemar's Test P-Value : 0.1948506
##
## Sensitivity : 0.6939
## Specificity : 0.6640
## Pos Pred Value : 0.6182
## Neg Pred Value : 0.7345
## Prevalence : 0.4395
## Detection Rate : 0.3049
## Detection Prevalence : 0.4933
## Balanced Accuracy : 0.6789
##
## 'Positive' Class : 1
##
Dari hasil confusionMatrix() di atas kita dapat melihat rentang nilai yang cukup besar antara True Positive dan False Positive. Kita dapat melihat kebaikan dari sebuah model ini berdasarkan nilai Sensitivity. Model yang kita punya memiliki nilai Sensitivity 0.7327.
Metode K-NN ini akan mengklasifikasikan data test berdasarkan karakteristik data train. Klasifikasi ditentukan berdasarkan kedekatan karakteristik yang diukur dengan cara Euclidean Distance atau menghitung jarak. Setelah itu dipilih menggunakan majority voting sesuai dengan nilai k yang sudah kita tentukan. Nilai k adalah jumlah data terdekat dari data test. Untuk mendapatkan nilai k yang optimal kita dapat mengakar kuadratkan jumlah baris pada data kita.
sqrt(nrow(pasien_jantung_train))## [1] 29.84962
Kita akan memisahkan terlebih dahulu variabel target dan variabel prediktor sebelum membuat model K-NN.
pasien_jantung_train_x <- pasien_jantung_train[,-c(1,4,6,7,8,9,16)]
pasien_jantung_test_x <- pasien_jantung_test[,-c(1,4,6,7,8,9,16,17,18)]
pasien_jantung_train_y <- pasien_jantung_train$TenYearCHD
pasien_jantung_test_y <- pasien_jantung_test$TenYearCHDBerikut adalah proses scaling pada variabel prediktor di data train dan data test.
pasien_jantung_train_xs <- scale(pasien_jantung_train_x)
pasien_jantung_test_xs <- scale(pasien_jantung_test_x ,
center = attr(pasien_jantung_train_xs,"scaled:center"),
scale = attr(pasien_jantung_train_xs,"scaled:scale"))Berikut adalah prediksi KNN menggunakan fungsi knn().
pasien_jantung_pred <- knn(train = pasien_jantung_train_xs,
test = pasien_jantung_test_xs,
cl = pasien_jantung_train_y,
k = 29)
head(pasien_jantung_pred)## [1] 1 0 1 0 1 1
## Levels: 0 1
Kemudian kita melakukan confusionMatrix untuk evaluasi model KNN.
confusionMatrix(data = pasien_jantung_pred,
reference = pasien_jantung_test_y,
positive = "1")## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 82 38
## 1 43 60
##
## Accuracy : 0.6368
## 95% CI : (0.5699, 0.6999)
## No Information Rate : 0.5605
## P-Value [Acc > NIR] : 0.01252
##
## Kappa : 0.2668
##
## Mcnemar's Test P-Value : 0.65672
##
## Sensitivity : 0.6122
## Specificity : 0.6560
## Pos Pred Value : 0.5825
## Neg Pred Value : 0.6833
## Prevalence : 0.4395
## Detection Rate : 0.2691
## Detection Prevalence : 0.4619
## Balanced Accuracy : 0.6341
##
## 'Positive' Class : 1
##
Model K-NN memiliki nilai Sensitivity yang lebih rendah dibanding dengan model sebelumnya.
Pada kasus data penyakit jantung ini, model Logistic Regression masih lebih baik dibanding dengan model K-NN. Kemungkinan karena faktor jenis kelamin dan diabetes cukup mempengaruhi penyakit jantung. Dimana jenis kelamin dan diabetes ini tidak digunakan pada model KNN karena merupakan variable kategorik.