Prediksi Pasien Penyakit Jantung
Pada kali ini, saya akan mencoba untuk melakukan prediksi terhadap pasien penyakit jantung pada suatu rumah sakit yang akan diprediksi sakit atau tidak. Algoritma yang digunakan yaitu logistic regission, K-Nearest Neighboor, Naive Bayes, Decision Tree dan Random Forest yang merupakan supervised learning.
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(class)
library(tidyr)
library(caret)
## Loading required package: lattice
## Loading required package: ggplot2
library(e1071)
library(caret)
library(ROCR)
library(partykit)
## Loading required package: grid
## Loading required package: libcoin
## Loading required package: mvtnorm
library(randomForest)
## randomForest 4.6-14
## Type rfNews() to see new features/changes/bug fixes.
##
## Attaching package: 'randomForest'
## The following object is masked from 'package:ggplot2':
##
## margin
## The following object is masked from 'package:dplyr':
##
## combine
Dataset yang saya gunakan yaitu data mengenai pasien yang terkena penyakit jantung berdasarkan beberapa karakteristik yang menyertai yang dapat diunduh pada link berikut https://www.kaggle.com/ronitf/heart-disease-uci .
jantung <- read.csv("dataset/heart.csv")
glimpse(jantung)
## Rows: 303
## Columns: 14
## $ ï..age <int> 63, 37, 41, 56, 57, 57, 56, 44, 52, 57, 54, 48, 49, 64, 58...
## $ sex <int> 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0...
## $ cp <int> 3, 2, 1, 1, 0, 0, 1, 1, 2, 2, 0, 2, 1, 3, 3, 2, 2, 3, 0, 3...
## $ trestbps <int> 145, 130, 130, 120, 120, 140, 140, 120, 172, 150, 140, 130...
## $ chol <int> 233, 250, 204, 236, 354, 192, 294, 263, 199, 168, 239, 275...
## $ fbs <int> 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 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...
## $ thalach <int> 150, 187, 172, 178, 163, 148, 153, 173, 162, 174, 160, 139...
## $ exang <int> 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 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...
## $ slope <int> 0, 0, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 0, 2, 2...
## $ ca <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2...
## $ thal <int> 1, 2, 2, 2, 2, 1, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2...
## $ target <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1...
Informasi penting yang terdapat didata:
i..age : umur data tahun sex : jenis kelamin (1 = laki-laki, 0 = perempuan) cp : tipe nyeri yang paling parah trestbps : melacak tekanan darah (dalam mm Hg saat masuk kerumah sakit) chol : Kolesterol dalam mg / dl fbs : gula darah dalam keadaan puasa >120 mg / dl (1 = benar, 0 = salah) restecg : mengembalikan hasil elektokardiografi thalach : denyut jantung maksimum tercapai exang : exercise included angina (1 = ya, 0 = tidak) oldpeak : ST depresi yang disebabkan oleh olahraga 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, 0 = tidak)
head(jantung)
Pada beberapa variabel yang digunakan, terdapat ketidaksesuaian tipe data dan tipe data yang tidak digunakan maka harus disesuaikan.
jantung <- jantung %>%
select(-ï..age) %>%
mutate_if(is.integer, as.factor) %>%
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("Tidak", "Iya")),
target = factor(target, levels = c(0,1),
labels = c("Sehat", "Tidak Sehat")))
glimpse(jantung)
## Rows: 303
## Columns: 13
## $ sex <fct> Male, Male, Female, Male, Female, Male, Female, Male, Male...
## $ cp <fct> 3, 2, 1, 1, 0, 0, 1, 1, 2, 2, 0, 2, 1, 3, 3, 2, 2, 3, 0, 3...
## $ trestbps <fct> 145, 130, 130, 120, 120, 140, 140, 120, 172, 150, 140, 130...
## $ chol <fct> 233, 250, 204, 236, 354, 192, 294, 263, 199, 168, 239, 275...
## $ fbs <fct> True, False, False, False, False, False, False, False, Tru...
## $ restecg <fct> 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1...
## $ thalach <fct> 150, 187, 172, 178, 163, 148, 153, 173, 162, 174, 160, 139...
## $ exang <fct> Tidak, Tidak, Tidak, Tidak, Iya, Tidak, Tidak, Tidak, Tida...
## $ 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...
## $ slope <fct> 0, 0, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 0, 2, 2...
## $ ca <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2...
## $ thal <fct> 1, 2, 2, 2, 2, 1, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2...
## $ target <fct> Tidak Sehat, Tidak Sehat, Tidak Sehat, Tidak Sehat, Tidak ...
colSums(is.na(jantung))
## sex cp trestbps chol fbs restecg thalach exang
## 0 0 0 0 0 0 0 0
## oldpeak slope ca thal target
## 0 0 0 0 0
prop.table(table(jantung$target))
##
## Sehat Tidak Sehat
## 0.4554455 0.5445545
Jika kita lihat dari proposi kedua kelas tersebut sudah cukup seimbang dan tidak membutuhkan pre-processing tambahan untuk perlu menyeimbangi proporsi antar kedua kelas target variabel.
Selanjutnya adalah melakukan splitting data menjadi data train dan data test. Splitting data train dan test ini bertujuan untuk membuat model yang digunakan yaitu data test dan data test tujuannya untuk menguji model terhadap unseen data. Splitting data train dan test ini saya menggunakan proporsi 80% untuk data train dan 20% untuk data test
RNGkind(sample.kind = "Rounding") # tambahan khusus u/ R 3.6 ke atas
## Warning in RNGkind(sample.kind = "Rounding"): non-uniform 'Rounding' sampler
## used
set.seed(406)
intrain <- sample(nrow(jantung), nrow(jantung)*0.8)
jantung.train <- jantung[intrain,]
jantung.test <- jantung[-intrain,]
prop.table(table(jantung.train$target))
##
## Sehat Tidak Sehat
## 0.4421488 0.5578512
Dari hasil re-check tersebut hasil yang saya peroleh bahwa data train nya masih seimbang dan tidak perlu pre-processing tambahan lagi untuk data train.
Membuat model dengan menggunakan regresi logistik. Pemodelan menggunakan fungsi glm() dalam membuat model regresi logistik. Variabel yang digunakan adalah variabel yang dianggap mempengaruhi target variabel.
model_jantung <- glm(formula = target~sex+cp+fbs+exang+oldpeak+slope+ca+thal, family = "binomial",
data = jantung.train)
summary(model_jantung)
##
## Call:
## glm(formula = target ~ sex + cp + fbs + exang + oldpeak + slope +
## ca + thal, family = "binomial", data = jantung.train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -3.2145 -0.2631 0.0989 0.4064 2.2551
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -1.2217 4.5904 -0.266 0.790138
## sexMale -1.3481 0.6009 -2.244 0.024860 *
## cp1 1.3301 0.6258 2.125 0.033562 *
## cp2 2.8983 0.6698 4.327 1.51e-05 ***
## cp3 2.4023 0.7726 3.109 0.001876 **
## fbsTrue 0.6027 0.7244 0.832 0.405420
## exangIya -0.8391 0.5059 -1.658 0.097219 .
## oldpeak -0.5814 0.2821 -2.061 0.039307 *
## slope1 -0.2145 1.0679 -0.201 0.840779
## slope2 1.9254 1.1406 1.688 0.091415 .
## ca1 -2.9971 0.6229 -4.812 1.50e-06 ***
## ca2 -3.5460 0.9591 -3.697 0.000218 ***
## ca3 -2.3189 1.1674 -1.986 0.046991 *
## ca4 -1.6238 4.3064 -0.377 0.706115
## thal1 3.2105 4.5067 0.712 0.476230
## thal2 2.9068 4.4422 0.654 0.512886
## thal3 1.1658 4.4363 0.263 0.792716
## ---
## 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: 138.64 on 225 degrees of freedom
## AIC: 172.64
##
## Number of Fisher Scoring iterations: 6
backward_jantung <- step(object = model_jantung, direction = "backward", trace = F)
summary(backward_jantung)
##
## Call:
## glm(formula = target ~ sex + cp + exang + oldpeak + slope + ca +
## thal, family = "binomial", data = jantung.train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -3.2314 -0.2298 0.1041 0.4155 2.2533
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -0.8317 5.2370 -0.159 0.873820
## sexMale -1.3372 0.6031 -2.217 0.026601 *
## cp1 1.3210 0.6164 2.143 0.032119 *
## cp2 2.9753 0.6653 4.472 7.75e-06 ***
## cp3 2.5017 0.7755 3.226 0.001256 **
## exangIya -0.8326 0.5036 -1.653 0.098284 .
## oldpeak -0.5944 0.2837 -2.095 0.036188 *
## slope1 -0.3144 1.0508 -0.299 0.764797
## slope2 1.7667 1.1134 1.587 0.112588
## ca1 -2.8835 0.5968 -4.832 1.35e-06 ***
## ca2 -3.5289 0.9719 -3.631 0.000282 ***
## ca3 -2.2234 1.1667 -1.906 0.056687 .
## ca4 -1.3725 4.9584 -0.277 0.781923
## thal1 3.0237 5.1840 0.583 0.559711
## thal2 2.6426 5.1210 0.516 0.605840
## thal3 0.9049 5.1175 0.177 0.859654
## ---
## 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: 139.34 on 226 degrees of freedom
## AIC: 171.34
##
## Number of Fisher Scoring iterations: 6
Dengan menggunakan backward_jantung hasil dari stepwise backward akan dihitung prediksi probability sakit atau tidak untuk jantung.test.
jantung.test$pred.Risk <- predict(backward_jantung, newdata = jantung.test, type = "response")
head(jantung.test)
Sekarang dengan mengklasifikasikan jantung.test berdasarkan pred.Risk dengan syarat lebih besar dari 0.5.
jantung.test$pred.Label <- ifelse(jantung.test$pred.Risk > 0.5, "Tidak Sehat","Sehat")
jantung.test$pred.Label <- as.factor(jantung.test$pred.Label)
# jantung.test$pred.Label <- ifelse(jantung.test$pred.Risk > 0.5, yes = "Tidak Sehat", no = "Sakit")
#
# jantung.test$pred.Label <- as.factor(jantung.test$pred.Label)
Sekarang kita melihat hasil prediksi berdasarkan klasifikasi yang telah dibuat diatas.
jantung.test %>%
select(target, pred.Label) %>%
head(10)
Dalam mengevaluasi model yang telah dibuat dengan menggunakan confusion matrix.
model_logistic <- confusionMatrix(jantung.test$pred.Label, jantung.test$target, positive = "Tidak Sehat")
model_logistic
## Confusion Matrix and Statistics
##
## Reference
## Prediction Sehat Tidak Sehat
## Sehat 27 5
## Tidak Sehat 4 25
##
## Accuracy : 0.8525
## 95% CI : (0.7383, 0.9302)
## No Information Rate : 0.5082
## P-Value [Acc > NIR] : 1.821e-08
##
## Kappa : 0.7047
##
## Mcnemar's Test P-Value : 1
##
## Sensitivity : 0.8333
## Specificity : 0.8710
## Pos Pred Value : 0.8621
## Neg Pred Value : 0.8438
## Prevalence : 0.4918
## Detection Rate : 0.4098
## Detection Prevalence : 0.4754
## Balanced Accuracy : 0.8522
##
## 'Positive' Class : Tidak Sehat
##
Berdasarkan hasil confusion matrix diatas, dapat diketahui bahwa kemampuan model dalam menebak target “1” atau “Sakit atau Tidak Sehat” sebesar 83.33% untuk Recall/ Sensitivity, 87.10% untuk specificity, 86.21% untuk Pos Pred Value / Precision dan 85.25% untuk Accuracy. Sehingga pada model ini bisa menebak pasien yang Tidak Sehat sebanyak 87.10%, karena saya menggunggulkan atau mengandalkan matriks precision yang bertujuan untuk berhati-hati untuk ambil pasien yang Tidak Sehat.
data_knn <- dummyVars(formula = ~target+sex+cp+fbs+exang+oldpeak+slope+ca+thal, data = jantung)
data_knn <- data.frame(predict(data_knn, newdata = jantung))
str(data_knn)
## 'data.frame': 303 obs. of 25 variables:
## $ target.Sehat : num 0 0 0 0 0 0 0 0 0 0 ...
## $ target.Tidak.Sehat: 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.Tidak : num 1 1 1 1 0 1 1 1 1 1 ...
## $ exang.Iya : 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 ...
data_knn$target.Sehat <- NULL
data_knn$sex.Female <- NULL
data_knn$fbs.False <- NULL
data_knn$exang.No <- NULL
names(data_knn)
## [1] "target.Tidak.Sehat" "sex.Male" "cp.0"
## [4] "cp.1" "cp.2" "cp.3"
## [7] "fbs.True" "exang.Tidak" "exang.Iya"
## [10] "oldpeak" "slope.0" "slope.1"
## [13] "slope.2" "ca.0" "ca.1"
## [16] "ca.2" "ca.3" "ca.4"
## [19] "thal.0" "thal.1" "thal.2"
## [22] "thal.3"
Pada kali ini saya melakukan split data train dan data test lagi akan digunakan pada klasifikasi k-nn
RNGkind(sample.kind = "Rounding") # tambahan khusus u/ R 3.6 ke atas
## Warning in RNGkind(sample.kind = "Rounding"): non-uniform 'Rounding' sampler
## used
set.seed(406)
intrain_knn <- sample(nrow(data_knn), nrow(data_knn)*0.8)
data_knn.train <- data_knn[intrain_knn,]
data_knn.test <- data_knn[-intrain_knn,]
#prediktor
jantung.train_x <- data_knn.train %>%
select(-target.Tidak.Sehat)
jantung.test_x <- data_knn.train %>%
select(-target.Tidak.Sehat)
# target
jantung.train_y <- data_knn.train %>%
select(target.Tidak.Sehat)
jantung.test_y <- data_knn.train %>%
select(target.Tidak.Sehat)
Data prediktor akan discaling menggunakan z-score standarization. Data test juga harus discalling dengan menggunakan parameter dari data train karena data test tersebut merupakan unseen data.
jantung.train_xs <- scale(jantung.train_x)
jantung.test_xs <- scale(x = jantung.test_x,
center = attr(jantung.train_xs, "scaled:center"),
scale = attr(jantung.train_xs, "scaled:scale"))
Sekarang, saya akan mencari nilai untuk nilai k. Mencari nilai optimum k ini bertujuan untuk mengelompokkan jumlah tetangga terdekat.
round(sqrt(nrow(jantung.train)))
## [1] 16
Disini jumlah kelas target saya adalah 2 yaitu Sehat dan Tidak Sehat. Sedangkan nilai optimum k saya yaitu 16. Oleh karena itu saya menaikan nilai k menjadi 17. Dengan tujuan supaya menghindari seri.
jantung.pred <- knn(train = jantung.train_xs, test = jantung.test_xs, cl = jantung.train_y$target.Tidak.Sehat, k = 17)
dim(jantung.train_xs)
## [1] 242 21
dim(jantung.test_xs)
## [1] 242 21
head(jantung.pred)
## [1] 0 1 1 1 0 0
## Levels: 0 1
Sekarang saya melakukan model evaluasi dengan menggunakan klasifikasi k-nn.
model_knn <- confusionMatrix(data = as.factor(jantung.pred), reference = as.factor(jantung.train_y$target.Tidak.Sehat), positive = "1")
model_knn
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 80 9
## 1 27 126
##
## Accuracy : 0.8512
## 95% CI : (0.8, 0.8936)
## No Information Rate : 0.5579
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.6931
##
## Mcnemar's Test P-Value : 0.004607
##
## Sensitivity : 0.9333
## Specificity : 0.7477
## Pos Pred Value : 0.8235
## Neg Pred Value : 0.8989
## Prevalence : 0.5579
## Detection Rate : 0.5207
## Detection Prevalence : 0.6322
## Balanced Accuracy : 0.8405
##
## 'Positive' Class : 1
##
Berdasarkan hasil confusion matrix diatas, dapat diketahui bahwa kemampuan model dalam menebak target “1” atau “Sakit atau Tidak Sehat” sebesar 93.33% untuk Recall/ Sensitivity, 74.77% untuk specificity, 82.35% untuk Pos Pred Value / Precision dan 85.12% untuk Accuracy. Sehingga pada model ini bisa menebak pasien yang Tidak Sehat sebanyak 82.35%, karena saya menggunggulkan atau mengandalkan matriks precision yang bertujuan untuk berhati-hati untuk ambil pasien yang Tidak Sehat.
length(jantung.pred)
## [1] 242
length(jantung.train_y$target.Tidak.Sehat)
## [1] 242
Selanjutnya dari dataset heart tersebut, saya akan melanjutkan untuk dipakai untuk model klasifikasi lain seperti klasifikasi Naive Bayes, Decision Tree dan Random Forest.
Pada kali ini, saya tidak melakukan proses cross validation lagi. Karena saya masih menggunakan cross validation data yang awal. Tetapi saya menggunakan klasifikasi naive bayes untuk melakukan prediksi pasien yang sakit atau tidak.
jantung_naive <- naiveBayes(x = jantung.train %>%
select(-target), y = jantung.train$target, laplace = 1)
Setelah membangun model naiveBayes, selanjutnya akan menghitung prediksi terhadap model klasifikasi naiveBayes.
jantung_predClass <- predict(object = jantung_naive, newdata = jantung.test, type = "class")
table(jantung_predClass)
## jantung_predClass
## Sehat Tidak Sehat
## 33 28
head(jantung_predClass)
## [1] Sehat Tidak Sehat Tidak Sehat Tidak Sehat Tidak Sehat Tidak Sehat
## Levels: Sehat Tidak Sehat
Dari data diatas yang saya dapatkan yaitu terdapat pasien yang sehat sebanyak 33 dan pasien yang tidak sehat sebanyak 28 orang. Sekarang mari kita lihat nilai confusion matrix nya.
model_naive <- confusionMatrix(data = jantung_predClass, reference = jantung.test$target, positive = "Tidak Sehat")
model_naive
## Confusion Matrix and Statistics
##
## Reference
## Prediction Sehat Tidak Sehat
## Sehat 27 6
## Tidak Sehat 4 24
##
## Accuracy : 0.8361
## 95% CI : (0.7191, 0.9185)
## No Information Rate : 0.5082
## P-Value [Acc > NIR] : 9.418e-08
##
## Kappa : 0.6717
##
## Mcnemar's Test P-Value : 0.7518
##
## Sensitivity : 0.8000
## Specificity : 0.8710
## Pos Pred Value : 0.8571
## Neg Pred Value : 0.8182
## Prevalence : 0.4918
## Detection Rate : 0.3934
## Detection Prevalence : 0.4590
## Balanced Accuracy : 0.8355
##
## 'Positive' Class : Tidak Sehat
##
Berdasarkan hasil confusion matrix diatas, dapat diketahui bahwa kemampuan model dalam menebak target “1” atau “Sakit atau Tidak Sehat” sebesar 80% untuk Recall/ Sensitivity, 87.10% untuk specificity, 85.71% untuk Pos Pred Value / Precision dan 83.61% untuk Accuracy. Sehingga pada model ini bisa menebak pasien yang Tidak Sehat sebanyak 83.61%, karena saya menggunggulkan atau mengandalkan matriks precision yang bertujuan untuk berhati-hati untuk ambil pasien yang Tidak Sehat.
Sekarang mari kita lihat ROC dan AUC nya untuk melihat apakah model bagus memprediksi atau tidak
# ambil hasil prediksi probability
jantung_predProb <- predict(jantung_naive, newdata = jantung.test, type = "raw")
head(jantung_predProb)
## Sehat Tidak Sehat
## [1,] 0.991893646 0.008106354
## [2,] 0.094905448 0.905094552
## [3,] 0.003476432 0.996523568
## [4,] 0.270079080 0.729920920
## [5,] 0.304411461 0.695588539
## [6,] 0.126685075 0.873314925
Selanjutnya kita buat ROC dengan menyiapkan objek prediction.
# buat objek prediction
jantung_roc <- prediction(predictions = jantung_predProb[, 1], labels = as.numeric(jantung.test$target == "Sehat"))
# buat performance dari objek prediction
perf <- performance(prediction.obj = jantung_roc, measure = "tpr", x.measure = "fpr")
# buat plot
plot(perf)
abline(0,1, lty = 2)
Selanjutnya setelah ROC, selanjutnya kita mencari AUC. Semakin mendekati 1, semakin bagus performa modelnya.
auc <- performance(prediction.obj = jantung_roc, measure = "auc")
auc@y.values
## [[1]]
## [1] 0.8580645
Nilai AUC dari Naive Bayes mendekati 1, artinya model kita mampu mengklasifikasikan kelas positif dengan baik tetapi kelas negatif dengan tidak baik.
Tahap selanjutnya yaitu dengan menggunakan klasifikasi yaitu decision tree.
jantung_dtree <- ctree(formula = target ~ . , data = jantung.train, control = ctree_control(mincriterion = 0.95, minsplit = 0, minbucket = 0))
plot(jantung_dtree, type = "simple")
Setelah dapat tree tersebut, mari lakukan evaluasi untuk melihat apakah model terjadi overfitting atau tidak.
jantung_predict <- predict(object = jantung_dtree, newdata = jantung.train %>%
select(-target), type = "response")
model_decision_tree_train <- confusionMatrix(data = jantung_predict, reference = jantung.train$target, positive = "Tidak Sehat")
model_decision_tree_train
## Confusion Matrix and Statistics
##
## Reference
## Prediction Sehat Tidak Sehat
## Sehat 76 12
## Tidak Sehat 31 123
##
## Accuracy : 0.8223
## 95% CI : (0.7682, 0.8683)
## No Information Rate : 0.5579
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.633
##
## Mcnemar's Test P-Value : 0.006052
##
## Sensitivity : 0.9111
## Specificity : 0.7103
## Pos Pred Value : 0.7987
## Neg Pred Value : 0.8636
## Prevalence : 0.5579
## Detection Rate : 0.5083
## Detection Prevalence : 0.6364
## Balanced Accuracy : 0.8107
##
## 'Positive' Class : Tidak Sehat
##
Dari hasil confusionMatrix decision tree diatas, performa model tidak cukup baik untuk matriks Pos Pred Value atau Precision. Coba cek apakah hal tersebut terjadi overfitting.
jantung_predict_test <- predict(object = jantung_dtree, newdata = jantung.test %>%
select(-target), type = "response")
model_decision_tree_test <- confusionMatrix(data = jantung_predict_test, reference = jantung.test$target, positive = "Tidak Sehat")
Dari percobaan diatas, hasil yang saya dapatkan adalah terjadi overfitting yang tidak terlalu ekstrim antara jantung.train dan jantung.test pada matriks Pos Pred Value. Matriks Pos Pred Value di jantung.train sebesar 79.87% sedangkan pada jantung.test sebesar 80.65%.
Selanjutnya, saya melakukan prediksi dengan menggunakan klasifikasi yaitu decision tree.
pred <- predict(jantung_dtree, jantung.test)
table(pred, jantung.test$target)
##
## pred Sehat Tidak Sehat
## Sehat 25 5
## Tidak Sehat 6 25
Dari hasil prediksi menggunakan Decision Tree diatas menghasilkan terdapat pasien Sehat yang benar di prediksi Sehat sebanyak 25 dan salah diprediksi Tidak Sehat sebanyak 5. Demikian pula untuk pasien yang Tidak Sehat yang diprediksi benar Tidak Sehat sebanyak 25 dan pasien yang salah diprediksi sebanyak 6 pasien.
Selanjutnya dari klasifikasi decision tree, maka akan melakukan uji coba lagi, agar dapat memperoleh nilai Pos Pred Value yang begitu tinggi dan baik. Klasifikasi yang ingin di terapkan yaitu klasifikasi Random Forest.
RNGkind(sample.kind = "Rounding")
## Warning in RNGkind(sample.kind = "Rounding"): non-uniform 'Rounding' sampler
## used
set.seed(99)
ctrl <- trainControl(method = "repeatedcv", number = 4, repeats = 3)
model_forest_jantung <- train(target ~ ., data = jantung.train, method = "rf", trainControl = ctrl)
saveRDS(model_forest_jantung, "model_forest_jantung.RDS")
model_forest_jantung <- readRDS("model_forest_jantung.RDS")
model_forest_jantung
## Random Forest
##
## 242 samples
## 12 predictor
## 2 classes: 'Sehat', 'Tidak Sehat'
##
## No pre-processing
## Resampling: Bootstrapped (25 reps)
## Summary of sample sizes: 242, 242, 242, 242, 242, 242, ...
## Resampling results across tuning parameters:
##
## mtry Accuracy Kappa
## 2 0.6036246 0.1016618
## 154 0.7710483 0.5333649
## 307 0.7569065 0.5021752
##
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was mtry = 154.
Selanjutnya, coba kita lihat Out of Bag Error yang dihasilkan dari klasifikasi random forest yang dihasilkan.
model_forest_jantung$finalModel
##
## Call:
## randomForest(x = x, y = y, mtry = param$mtry, trainControl = ..1)
## Type of random forest: classification
## Number of trees: 500
## No. of variables tried at each split: 154
##
## OOB estimate of error rate: 22.31%
## Confusion matrix:
## Sehat Tidak Sehat class.error
## Sehat 83 24 0.2242991
## Tidak Sehat 30 105 0.2222222
plot(model_forest_jantung$finalModel)
legend("topright", colnames(model_forest_jantung$finalModel$err.rate),
col=1:6,cex=0.8,fill=1:6)
Dengan mtry = 154, variabel yang didapatkan model dapat memprediksi data yang pasien Tidak Sehat atau pasien penyakit jantung sebanyak 105 sedangkan yang salah diprediksi pasien Sehat, seharusnya pasien Tidak Sehat sebanyak 30 pasien. Sedangkan pasien sehat yang diprediksi Tidak Sehat sebanyak 24.
Pada model_forest_jantung tersebut nilai Out of Bag sebesar 22.31%. Dengan kata lain, akurasi model pada unseen data adalah 77.69%.
Selanjutnya kita melihat prediktor apa saja yang paling berpengaruh terhadap target.
varImp(model_forest_jantung)
## rf variable importance
##
## only 20 most important variables shown (out of 307)
##
## Overall
## thal2 100.000
## oldpeak 75.513
## thal3 43.495
## exangIya 42.588
## slope2 28.749
## ca1 21.693
## cp2 21.538
## ca2 12.791
## slope1 11.714
## cp3 10.349
## sexMale 9.971
## ca3 8.032
## chol330 7.726
## chol335 6.740
## chol237 6.673
## thalach178 5.981
## cp1 5.880
## thalach125 5.498
## thalach162 5.101
## trestbps110 4.987
Dari tabel diatas, prediktor yang paling tinggi terhadap target yaitu thal2 yang mendapatkan hasil overall sebanyak 100.
jantung_predict_forest <- predict(model_forest_jantung, jantung.test)
Selanjutnya mari kita lihat confusionMatrix yang berasal dari klasifikasi Random Forest.
confusionMatrix(data = jantung_predict_forest, reference = jantung.test$target, positive = "Tidak Sehat")
## Confusion Matrix and Statistics
##
## Reference
## Prediction Sehat Tidak Sehat
## Sehat 28 7
## Tidak Sehat 3 23
##
## Accuracy : 0.8361
## 95% CI : (0.7191, 0.9185)
## No Information Rate : 0.5082
## P-Value [Acc > NIR] : 9.418e-08
##
## Kappa : 0.6713
##
## Mcnemar's Test P-Value : 0.3428
##
## Sensitivity : 0.7667
## Specificity : 0.9032
## Pos Pred Value : 0.8846
## Neg Pred Value : 0.8000
## Prevalence : 0.4918
## Detection Rate : 0.3770
## Detection Prevalence : 0.4262
## Balanced Accuracy : 0.8349
##
## 'Positive' Class : Tidak Sehat
##
Berdasarkan hasil confusion matrix diatas, dapat diketahui bahwa kemampuan model dalam menebak target “1” atau “Sakit atau Tidak Sehat” sebesar 76.67% untuk Recall/ Sensitivity, 90.32% untuk specificity, 88.46% untuk Pos Pred Value / Precision dan 83.61% untuk Accuracy. Sehingga pada model ini bisa menebak pasien yang Tidak Sehat sebanyak 88.46%, karena saya menggunggulkan atau mengandalkan matriks precision yang bertujuan untuk berhati-hati untuk ambil pasien yang Tidak Sehat.
Dari ke -5 klasifikasi yang saya gunakan yaitu klasifikasi Logistic Regression, K-Nearst Neighboor, Naive Bayes, Decision Tree dan Random Forest, hasil yang diperoleh dengan mengunggulkan Pos Pred Value / Precision yaitu: * Logistic Regression : 80.65% * Naive Bayes : 85.71% * K-Nearst Neighboor : 82.35% * Logistic Regression : 86.21% * Random Forest : 88.46%
Dari ke -5 klasifikasi tersebut, jika saya seorang dokter yang ingin mendeteksi pasien yang berisiko memiliki penyakit jantung atau dalam kondisi Tidak Sehat maka saya menggunakan metode Random Forest, karena Random Forest akan menghasilkan nilai Pos Pred Value yang lumayan tinggi dari ke-4 klasifikasi yang lain sebesar 88.46%. Dengan menggunakan Random Forest juga dapat melihat bahwa prediktor yang memiliki nilai information gain yang tinggi, prediktor tersebut yaitu thal2 yang memiliki nilai information gain sebesar 100.