Klasifikasi-Penyakit-Jantung
1. Explanation
Permulaan
Hallo ini adalah Rpubs kelima saya mengenai Supervised Learning Dengan Metode Klasifikasi, semoga bermanfaat :)
Tentang Data
Dataset ini adalah dataset mengenai penyakit jantung yang sekiranya dapat memprediksi karakteristik dari pada orang yang terkena penyakit jantung
Beberapa kolom yang terdapat di dalam nya :
- Age => Usia
- Sex => Jenis Kelamin (1 = male; 0 = female)
- cp => Jenis Nyeri Dada (4 Jenis)
- trestbps => Tekanan darah istirahat (dalam mm Hg saat masuk ke rumah sakit)
- chol => Kolesterol serum dalam mg/dl
- fbs > Gula darah puasa > 120 mg/dl (1 = benar; 0 = salah)
- restecg => Hasil elektrokardiografi istirahat
- thalach => Detak jantung maksimum tercapai
- exang => angina akibat olahraga (1 = ya; 0 = tidak)
- oldpeak => ST depression induced by exercise relative to rest => Depresi ST yang diinduksi oleh olahraga relatif terhadap istirahat
- target => 1 = terkena penyakit jantung, 0 = tidak terkena penyakit jantung
Business Goal
Di sini saya membuat Role-Play yang dimana saya bekerja sebagai seorang Data Scientist di salah satu Rumah Sakit NTB. Kemudian saya ingin membuat model machine learning dengan metode klasifikasi yang tujuan nya nanti untuk mengurangi orang yang penyakit jantung tetapi di anggap tidak terkena penyakit jantung yang dimana hal itu cukup berbahaya ketika orang tersebut harus nya mendapat treatment khusus tetapi malah di anggap tidak sakit jantung.
Setup Library
kita disini akan membutuhkan beberapa library baik itu untuk visualisasi maupun pembentukan model
library(tidyverse)
library(broom)
library(ggplot2)
library(modelr)
library(pscl)
library(caret)
library(class)2. Logistic Resgression
2.1 Data Wrangling / Exploratory Data Analysis
Baca Dataset
heart <- read.csv("heart.csv")Selesai, mari lanjutkan ke langkah berikutnya
str(heart)#> 'data.frame': 303 obs. of 14 variables:
#> $ age : int 63 37 41 56 57 57 56 44 52 57 ...
#> $ sex : int 1 1 0 1 0 1 0 1 1 1 ...
#> $ 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 : int 1 0 0 0 0 0 0 0 1 0 ...
#> $ 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 : int 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 : 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 : int 1 1 1 1 1 1 1 1 1 1 ...
Wah sepertinya banyak kolom yang sifat nya diskrit, mari kita ubah ke dalam bentuk factor
2.2 Data pre-processing
Ubah tipe data (explicit coertion)
heart <- heart %>%
mutate_at(c("sex", "cp", "fbs", "restecg", "exang", "slope", "ca", "thal", "target"), as.factor)str(heart)#> 'data.frame': 303 obs. of 14 variables:
#> $ age : int 63 37 41 56 57 57 56 44 52 57 ...
#> $ sex : Factor w/ 2 levels "0","1": 2 2 1 2 1 2 1 2 2 2 ...
#> $ cp : Factor w/ 4 levels "0","1","2","3": 4 3 2 2 1 1 2 2 3 3 ...
#> $ 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 "0","1": 2 1 1 1 1 1 1 1 2 1 ...
#> $ restecg : Factor w/ 3 levels "0","1","2": 1 2 1 2 2 2 1 2 2 2 ...
#> $ thalach : int 150 187 172 178 163 148 153 173 162 174 ...
#> $ exang : Factor w/ 2 levels "0","1": 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 : Factor w/ 3 levels "0","1","2": 1 1 3 3 3 2 2 3 3 3 ...
#> $ ca : Factor w/ 5 levels "0","1","2","3",..: 1 1 1 1 1 1 1 1 1 1 ...
#> $ thal : Factor w/ 4 levels "0","1","2","3": 2 3 3 3 3 2 3 4 4 3 ...
#> $ target : Factor w/ 2 levels "0","1": 2 2 2 2 2 2 2 2 2 2 ...
Yeayy tipe data nya sekarang sudah benar
Cek Mising Values
anyNA(heart)#> [1] FALSE
Sepertinya di keseluruhan dataset kita tidak terdapat Missing Values :D
Cek Outlier
# cek outlier
library(tidyverse)
heart %>%
select(-target) %>%
boxplotTerlihat pada kolom Chol memiliki Outlier yang paling banyak, tetapi bukan berarti Outlier yang terdapat di data kita harus serta merta di hapus, perlu analisa lebih lanjut. Dari hasil model yang kita buat nanti akan kita putuskan apakah dengan menghapus Outlier tingkat akurasi model kita meningkat atau tidak.
Cek duplikasi Data
duplicated(heart)#> [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [25] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [37] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [49] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [61] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [73] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [85] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [97] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [109] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [121] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [133] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [145] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [157] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE
#> [169] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [181] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [193] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [205] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [217] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [229] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [241] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [253] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [265] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [277] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [289] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [301] FALSE FALSE FALSE
heart[duplicated(heart)
,]#> age sex cp trestbps chol fbs restecg thalach exang oldpeak slope ca thal
#> 165 38 1 2 138 175 0 1 173 0 0 2 4 2
#> target
#> 165 1
Pengecekan Duplikasi data kita lakukan karena salah satu asumsi dari pada Logistic Regression ialah Independence of Observations yang artinya antar observasi saling independen & tidak berasal dari pengukuran berulang (repeated measurement). Tetapi karena disini hanya satu saja duplikasi data yang kita miliki saya asumsikan data duplikasi nya tidak perlu di hapus karena bisa jadi ini merupakan data yang tidak secara sengaja sama
Cek Proporsi Kelas Target
prop.table(table(heart$target))#>
#> 0 1
#> 0.4554455 0.5445545
Proporsi kelas target nya sudah cukup balance karena tidak berbeda secara extreme signifikan. Tujuan membalance kan variable target ialah agar model kita dapat secara merata / maksimal belajar di kedua kelas (positif / negatif)
2.3 Cross Validation
Splitting Data untuk Data Train dan Test dengan Proporsi 80:20
set.seed(100)
index <- sample(x = nrow(heart), size = nrow(heart)*0.8)
heart_log_train <- heart[index,]
heart_log_test <- heart[-index,]Cek Kembali Proporsi Kelas Target
prop.table(table(heart_log_train$target))#>
#> 0 1
#> 0.4586777 0.5413223
Aman masih balance :)
2.4 Bangun Model Logistic Regression
model_heart <- glm(target ~ ., heart_log_train, family = "binomial")
summary(model_heart)#>
#> Call:
#> glm(formula = target ~ ., family = "binomial", data = heart_log_train)
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -3.01967 -0.26369 0.08093 0.41056 3.02344
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -0.654417 4.032849 -0.162 0.87109
#> age 0.039524 0.030510 1.295 0.19517
#> sex1 -2.010269 0.695651 -2.890 0.00386 **
#> cp1 1.328030 0.721176 1.841 0.06555 .
#> cp2 1.932130 0.606357 3.186 0.00144 **
#> cp3 2.844880 0.897189 3.171 0.00152 **
#> trestbps -0.024394 0.014847 -1.643 0.10038
#> chol -0.005692 0.005845 -0.974 0.33012
#> fbs1 0.566675 0.684886 0.827 0.40801
#> restecg1 0.927854 0.484320 1.916 0.05539 .
#> restecg2 -1.222843 2.949019 -0.415 0.67839
#> thalach 0.012845 0.013419 0.957 0.33844
#> exang1 -0.681463 0.528283 -1.290 0.19707
#> oldpeak -0.201091 0.293392 -0.685 0.49309
#> slope1 0.043714 1.030234 0.042 0.96615
#> slope2 1.735690 1.133955 1.531 0.12586
#> ca1 -2.477713 0.628969 -3.939 0.0000817 ***
#> ca2 -4.107630 1.046618 -3.925 0.0000868 ***
#> ca3 -2.206260 1.019091 -2.165 0.03039 *
#> ca4 1.201098 1.977672 0.607 0.54363
#> thal1 2.545022 2.587681 0.984 0.32535
#> thal2 2.825288 2.490070 1.135 0.25653
#> thal3 1.073461 2.484951 0.432 0.66575
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 333.83 on 241 degrees of freedom
#> Residual deviance: 131.51 on 219 degrees of freedom
#> AIC: 177.51
#>
#> Number of Fisher Scoring iterations: 6
Dari hasil Summary Model kita di atas dapat kita simpulkan bahwa banyak prediktor variable yang tidak signifikan mempengaruhi target variable jika di perhatikan dari p-value nya, walaupun jika di lihat dari Residual Deviance nya cukup kecil
Model Feature Selection menggunakan Stepwise akan kita lakukan guna menseleksi variable prediktor yang signifikan mempengaruhi variable target
model_step <- step(model_heart, director = "backward", trace = F)
summary(model_step)#>
#> Call:
#> glm(formula = target ~ sex + cp + trestbps + restecg + exang +
#> slope + ca + thal, family = "binomial", data = heart_log_train)
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -3.1105 -0.2909 0.0779 0.4125 2.9766
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) 1.28957 3.85756 0.334 0.738155
#> sex1 -1.86680 0.65240 -2.861 0.004218 **
#> cp1 1.52414 0.70275 2.169 0.030096 *
#> cp2 2.13444 0.58958 3.620 0.000294 ***
#> cp3 2.94758 0.83834 3.516 0.000438 ***
#> trestbps -0.01985 0.01314 -1.511 0.130847
#> restecg1 0.84631 0.46948 1.803 0.071441 .
#> restecg2 -1.51197 2.07917 -0.727 0.467106
#> exang1 -0.80632 0.51154 -1.576 0.114967
#> slope1 0.13132 0.88952 0.148 0.882635
#> slope2 2.06320 0.93499 2.207 0.027338 *
#> ca1 -2.20116 0.58150 -3.785 0.000154 ***
#> ca2 -3.80848 0.91621 -4.157 0.0000323 ***
#> ca3 -2.01801 0.93290 -2.163 0.030529 *
#> ca4 1.51991 1.97982 0.768 0.442666
#> thal1 2.18485 3.37490 0.647 0.517383
#> thal2 2.50663 3.28511 0.763 0.445447
#> thal3 0.72976 3.28462 0.222 0.824179
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 333.83 on 241 degrees of freedom
#> Residual deviance: 135.95 on 224 degrees of freedom
#> AIC: 171.95
#>
#> Number of Fisher Scoring iterations: 6
Dapat dilihat sekarang variable prediktor kita sudah signifikan, walaupun Residual Deviance nya agak sedikit besar dari model sebelumnya. Dari hasil summary model kita di atas, dapat di intrepertasikan juga untuk nilai Prediktor nya dengan merubah nya ke dalam ods dan probabilitas. Misal nya Sex, untuk Sex1 (Male) akan 0.15 Kali lebih mungkin untuk tidak terkena penyakit jantung atau dengan kata lain hanya 13% untuk berpeluang terkena penyakit jantung dari pada Sex0 (Female) dengan nilai variable sama / konstan
2.5 Prediksi
heart_log_test$pred.Heart <- predict(model_step, newdata = heart_log_test, type = "response")
heart_log_test$pred.Label <- ifelse(heart_log_test$pred.Heart > 0.5, "1", "0")Cek hasil Prediksi
heart_log_test %>% select(target, pred.Label) %>% head(6)#> target pred.Label
#> 6 1 0
#> 10 1 1
#> 17 1 1
#> 21 1 0
#> 22 1 1
#> 23 1 1
Visualkan hasil prediksi
ggplot(heart_log_test, aes(x=pred.Heart)) +
geom_density(lwd=0.5) +
labs(title = "Distribution of Probability Prediction Data") +
theme_minimal()prop.table(table(heart_log_test$pred.Label))#>
#> 0 1
#> 0.3606557 0.6393443
Jika di lihat dari plot dan proporsi hasil prediksi kita, model kita lebih banyak memprediksi kelas Positive (1) => terkena penyakit jantung
2.6 Model Evaluasi
Kita akan mencoba mengevaluasi model nya menggunakan Confusion Matrix
log_conf <- confusionMatrix(data=as.factor(heart_log_test$pred.Label), reference=as.factor(heart_log_test$target), positive="1")
log_conf#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction 0 1
#> 0 17 5
#> 1 10 29
#>
#> Accuracy : 0.7541
#> 95% CI : (0.6271, 0.8554)
#> No Information Rate : 0.5574
#> P-Value [Acc > NIR] : 0.001178
#>
#> Kappa : 0.4919
#>
#> Mcnemar's Test P-Value : 0.301700
#>
#> Sensitivity : 0.8529
#> Specificity : 0.6296
#> Pos Pred Value : 0.7436
#> Neg Pred Value : 0.7727
#> Prevalence : 0.5574
#> Detection Rate : 0.4754
#> Detection Prevalence : 0.6393
#> Balanced Accuracy : 0.7413
#>
#> 'Positive' Class : 1
#>
Dari hasil Confusion Matrix diatas terhadap Model Logistic Regression kita dapat kita simpulkan
Recall / Sensitivity => Yang benar di prediksi positif dari yang realita nya (aktual nya) positif oleh Model kita ada 85%
Specifity => Yang benar di prediksi negatif dari yang realita nya (aktual nya) negatif oleh Model kita ada 62%
Precission Yang benar di prediksi positif dari yang di prediksi nya positif oleh Model kita ada 85%
Accuracy => Seberapa banyak yang benar di prediksi dari keseluruhan data (positif maupun negatif) oleh model kita ada 75%
Mari kita evaluasi Residual yang di miliki oleh Model kita
library(broom)
# meng-ekstrak informasi dari model yang kita punya menggunakan fungsi augment
model1_data <- augment(model_step) %>%
mutate(index = 1:n())
ggplot(model1_data, aes(index, .std.resid, color = target)) +
geom_point(alpha = .5) +
geom_ref_line(h = 3)Mari saya perjelas disini, bahwa Evaluasi Residual yang kita lakukan bukan bertujuan untuk mengasumsikan Normal Residual atau Error yang mendekati 0 seperti pada Linear Regression. Tetapi tujuan disini untuk melihat data point atau titik data yang mana yang tidak fit dengan model yang kita miliki, dengan cara melihat Standar Deviasi atau simpangan yang lebih dari pada 3. Dari hasil visualisasi di atas, dapat di simpulkan kalau titik data kita sudah cukup fit dengan model nya. Cuman ada 1 titik dari dari kelas 0 yang memiliki simpangan deviasi lebih dari 3. Disini saya menyimpulkan data tersebut bukan Outlier melainkan pola data yang baru kita temukan pada Dataset kita
Jika ingin di analisa lebih dalam Outlier nya. Kita dapat menggunakan Cook Distance yaitu perkiraan pengaruh titik data terhadap model atau dengan kata lain Cook Distance adalah ringkasan dari seberapa banyak model regresi kita berubah kita suatu observasi di hapus. Semakin besar nilai Cook Distance pada suatu observasi, maka akan semakin berpengaruh terhadap model jika observasi tersebut di hapus.
par(mfrow = c(2, 2))
plot(model_step)Dari ke empat plot diatas, mari kita fokus pada Plot Residuals vs Leverage. Dari plot tersebut dapat kita simpulkan ada beberapa Outlier yang terdapat pada data kita yang melewati Cook Distance (garis putus-putus merah) nya, tetapi rata-rata data kita sudah berada di dalam Cook Distance. Dapat kita lihat juga walaupun terdapat beberapa Outlier tetapi ternyata Outlier tersebut tidak mempengaruhi hasil prediksi dari pada model kita jika di lihat dari garis tengah nya yang constant lurus. Berarti kita tidak perlu menghapus Outlier pada data kita.
Evaluasi Berikut nya kita akan Mengguanakan Pseudo R. Karena pada Logistic Regression kita tidak punya yang R-Square seperti pada Linear Regression. Menurut McFadden, D. (1974) “Conditional logit analysis of qualitative choice behavior.” nilai yang tergolong bagus untuk hasil perhitungan McFadden ialah 0.2 - 0.4, tetapi pada dasarnya semakin mendekati 1 artinya model kita sudah Good Fit. Ide perhitungan dari pada McFadden ialah \[ 1 - \frac{ln(LM_0)}{ln(LM_1)}\] \(ln(LM_0)\) = Kemungkinan nilai Log dari Model dengan Prediktor yang kita sertakan \(ln(LM_1)\) = Kemungkinan nilai Log dari Model tanpa Prediktor hanya intercept sebagai prediktor
pR2(model_step)["McFadden"]#> fitting null model for pseudo-r2
#> McFadden
#> 0.5927473
Yeayyy, kita dapat nilai di atas 0.4 atau mendekati 1, artinya model kita sudah Good Fit
3. K-Nearest Neighbor
3.1 Data Pre-Processing
Mari kita ubah tipe data Target menjadi Factor. Untuk variable prediktor tidak perlu.
heart_two <- read.csv("heart.csv")
heart_two <- heart_two %>% mutate_at(c("target"), as.factor)Karena kita menggunakan K-NN, mari kita coba lihat range data kita
summary(heart)#> age sex cp trestbps chol fbs
#> Min. :29.00 0: 96 0:143 Min. : 94.0 Min. :126.0 0:258
#> 1st Qu.:47.50 1:207 1: 50 1st Qu.:120.0 1st Qu.:211.0 1: 45
#> Median :55.00 2: 87 Median :130.0 Median :240.0
#> Mean :54.37 3: 23 Mean :131.6 Mean :246.3
#> 3rd Qu.:61.00 3rd Qu.:140.0 3rd Qu.:274.5
#> Max. :77.00 Max. :200.0 Max. :564.0
#> restecg thalach exang oldpeak slope ca thal target
#> 0:147 Min. : 71.0 0:204 Min. :0.00 0: 21 0:175 0: 2 0:138
#> 1:152 1st Qu.:133.5 1: 99 1st Qu.:0.00 1:140 1: 65 1: 18 1:165
#> 2: 4 Median :153.0 Median :0.80 2:142 2: 38 2:166
#> Mean :149.6 Mean :1.04 3: 20 3:117
#> 3rd Qu.:166.0 3rd Qu.:1.60 4: 5
#> Max. :202.0 Max. :6.20
Terlihat dari summary data kita, masih terdapat range data yang berbeda. Ada baik nya kita menormalisasikan data kita agar perhitungan jarak yang di lakukan K-NN semakin baik
Normalisasikan Data dengan Z-Score, tetapi sebelum itu mari lakukan Cross Validation terlebih dahulu
set.seed(100)
index_two <- sample(nrow(heart_two), nrow(heart_two) * 0.8)
# prediktor
heart_train <- heart_two[index_two,]
heart_test <- heart_two[-index_two,]
prop.table(table(heart_train$target))#>
#> 0 1
#> 0.4586777 0.5413223
proporsi data target terlihat cukup balance
Buat variable prediktor dan target untuk data train dan test
# prediktor
heart_train_x <- heart_train %>% select(-target)
heart_test_x <- heart_test %>% select(-target)
# target
heart_train_y <- heart_train[,"target"]
heart_test_y <- heart_test[,"target"]Sekarang barulah kita melakukan Normalisasi dengan Z-Score
# scaling data prediktor
heart_train_xs <- scale(heart_train_x)
heart_test_xs <- scale(heart_test_x,
center = attr(heart_train_xs,"scaled:center"),
scale = attr(heart_train_xs, "scaled:scale"))Untuk Scaling terhadap data test kita menggunakan attribut dari data train (mean & Standar Deviasi nya) agar tidak menghasilkan perhitungan jarak yang berbeda antara data train dan test
3.2 Prediksi
Mari kita lakukan prediksi secara langsung karena K-NN tidak membuat model melainkan langsung menghitung jarak antar tetangga.
# memilih k yang optimum dengan mengakar kan jumlah observasi
sqrt(nrow(heart_train_xs))#> [1] 15.55635
# k = 15
# buat prediksi K-NN
heart_pred <- knn(train = heart_train_xs, test = heart_test_xs, cl = as.factor(heart_train_y), k = 15)Agar lebih jelas mari lihat Hasil dari pada Confusion Matrix nya
# confusion matrix
confusionMatrix(data=heart_pred, reference=heart_test_y, positive="1")#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction 0 1
#> 0 15 2
#> 1 12 32
#>
#> Accuracy : 0.7705
#> 95% CI : (0.645, 0.8685)
#> No Information Rate : 0.5574
#> P-Value [Acc > NIR] : 0.0004503
#>
#> Kappa : 0.5164
#>
#> Mcnemar's Test P-Value : 0.0161569
#>
#> Sensitivity : 0.9412
#> Specificity : 0.5556
#> Pos Pred Value : 0.7273
#> Neg Pred Value : 0.8824
#> Prevalence : 0.5574
#> Detection Rate : 0.5246
#> Detection Prevalence : 0.7213
#> Balanced Accuracy : 0.7484
#>
#> 'Positive' Class : 1
#>
Mari kita lihat dari hasil di Confusion Matrix di atas, kita memiliki Accuracy sebesar 77% yang artinya model kita mampu memprediski keseluruhan data baik positif maupun negatif sebesar 77%. Untuk Recall/Sensitivity atau Yang benar di prediksi positif dari yang realita nya (aktual nya) positif ialah 94% dan Precission nya atau Yang benar di prediksi positif dari yang di prediksi nya positif ada 72%
Mari kita lihat estimasi Error dari pada model K-NN kita
# error estimasi
error <- mean(heart_pred != heart_test_y)
error#> [1] 0.2295082
Apakah Error Tersebut besar atau kecil ? Maka kita harus melihat setiap jumlah k yang di gunakan untuk melihat Error terkecil dan terbesar nya
predicted.type <- NULL
error.rate <- NULL
# lakukan loop sampe dengan k optimum yakni 21
for (i in 1:21) {
predicted.type <- knn(heart_train_xs, heart_test_xs, heart_train_y, k=i)
error.rate[i] <- mean(predicted.type!=heart_test_y)
}
# buat data frame dari hasil error yang sudah kita dapatkan di atas
knn.error <- as.data.frame(cbind(k=1:21,error.type =error.rate))
# visualisasikan error rate nya berdasarkan k
ggplot(knn.error,aes(k,error.type))+
geom_point()+
geom_line() +
scale_x_continuous(breaks=1:21)+
theme_bw() +
xlab("Value of K") +
ylab('Error')Dapat di lihat dari line plot di atas, terdapat beberapa jumlah k yang memiliki nilai Error kecil. Tapi disini kita akan mencoba memilih jumlah k = 19 agar tidak terlalu kecil, karena jika kita memiliki jumlah data yang lumayan banyak observasi nya lalu kita memilih jumlah k yang kecil maka K-NN rentan mengklasifikasi kan data baru ke kelas Outlier
Mari latih ulang data kita dengan jumlah k = 19
heart_pred <- knn(train = heart_train_xs, test = heart_test_xs, cl = as.factor(heart_train_y), k = 19)Liat Estimasi Error
error <- mean(heart_pred != heart_test_y)
error#> [1] 0.2131148
Yeayy kita sudah memiliki Estimasi Error yang lebih kecil sekarang
Mari lihat Confusion Matrix nya
knn_conf <- confusionMatrix(data=heart_pred, reference=heart_test_y, positive="1")
knn_conf#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction 0 1
#> 0 15 1
#> 1 12 33
#>
#> Accuracy : 0.7869
#> 95% CI : (0.6632, 0.8814)
#> No Information Rate : 0.5574
#> P-Value [Acc > NIR] : 0.0001579
#>
#> Kappa : 0.5492
#>
#> Mcnemar's Test P-Value : 0.0055457
#>
#> Sensitivity : 0.9706
#> Specificity : 0.5556
#> Pos Pred Value : 0.7333
#> Neg Pred Value : 0.9375
#> Prevalence : 0.5574
#> Detection Rate : 0.5410
#> Detection Prevalence : 0.7377
#> Balanced Accuracy : 0.7631
#>
#> 'Positive' Class : 1
#>
Lihat dari segi Akurasi dan Recall menjadi lebih tinggi :)
4. Model Evaluation Logistic Regression dan K-NN
eval_logit <- data_frame(Accuracy = log_conf$overall[1],
Recall = log_conf$byClass[1],
Specificity = log_conf$byClass[2],
Precision = log_conf$byClass[3])
eval_knn <- data_frame(Accuracy = knn_conf$overall[1],
Recall = knn_conf$byClass[1],
Specificity = knn_conf$byClass[2],
Precision = knn_conf$byClass[3])Model Logistic Regression
eval_logit#> # A tibble: 1 × 4
#> Accuracy Recall Specificity Precision
#> <dbl> <dbl> <dbl> <dbl>
#> 1 0.754 0.853 0.630 0.744
Model K-NN
eval_knn#> # A tibble: 1 × 4
#> Accuracy Recall Specificity Precision
#> <dbl> <dbl> <dbl> <dbl>
#> 1 0.787 0.971 0.556 0.733
Dapat di lihat dari hasil evaluasi kedua model di atas, dari segi akurasi dan recall model K-NN jauh lebih baik dari pada Logistic Regression. Dan pada kasus kita sekarang, kita ingin mengurangi orang yang penyakit jantung tetapi di anggap tidak terkena penyakit jantung yang dimana hal itu cukup berbahaya ketika orang tersebut harus nya mendapat treatment khusus tetapi malah di anggap tidak sakit jantung. Berarti sudah sangat tepat jika kita memilih K-NN yang dimana memiliki nilai Recall lebih tinggi. Itu artinya kita sudah bisa dengan baik mengurangi kasus FN (False Negatif) dengan nilai Recall yang tinggi tersebut.
4. Conclusion
Dengan peran saya sebagai Data Scientist pada salah satu Rumah Sakit di NTB, yang dimana saya ingin mengurangi orang yang penyakit jantung tetapi di anggap tidak terkena penyakit jantung yang tentu hal tersebut sangat berbahaya, bahkan bisa membuat nyawa orang melayang. Karena seharusnya orang tersebut mendapatkan treatment penyakit jantung tetapi malah di anggap tidak memiliki penyakit jantung. Dengan itu saya memilih Metric Recall pada Model K-NN saya, yang dimana saya tidak ingin model yang saya buat memprediksi orang yang tidak terkena penyakit jantung tetapi sebenarnya terkena penyakit jantung.