1 Introduction

K-Nearest Neighbor (KNN) termasuk pada metode supervised machine learning yang dapat digunakan untuk masalah klasifikasi maupun regresi. Konsep dasar KNN adalah mencari k-nearest neighbors atau k tetangga terdekat dari data amatan berdasarkan jarak euclidean ataupun ukuran jarak lainnya. KNN umumnya digunakan untuk pemodelan klasifikasi namun dapat digunakan juga pada pemodelan regresi.

Dalam model klasifikasi, algoritma KNN menghitung jarak antara data amatan yang akan diklasifikasikan dengan dataset yang sudah memiliki label kelas, dan memilih k tetangga terdekat dengan data amatan tersebut berdasarkan ukuran jarak. Setelah itu, dari k tetangga tersebut, dilihat kelas apa yang paling banyak (mayoritas) menjadi tetangga terdekatnya. Hasil akhir yaitu mengklasifikasikan amatan tersebut masuk ke dalam kelas mayoritas yang diperoleh.

Langkah-Langkah KNN

Menentukan Parameter K:

  • K dalam KNN menunjukkan jumlah tetangga terdekat yang akan digunakan untuk membuat prediksi. Algoritma ini memeriksa label k tetangga terdekat suatu titik data dan menentukan label mayoritas sebagai prediksi.

    Pemilihan nilai k adalah bagian utama dari KNN. Nilai k yang terlalu kecil dapat menyebabkan model rentan terhadap noise, sedangkan nilai k yang besar dapat mengaburkan batas keputusan yang sebenarnya.

Menghitung Jarak:

  • Algoritma menghitung jarak antara titik data yang akan diprediksi dengan semua titik data dalam dataset pelatihan. Jarak ini dapat diukur menggunakan berbagai metrik, seperti Euclidean, Manhattan, atau Minkowski distance.

Menentukan Tetangga Terdekat:

  • Algoritma memilih k tetangga terdekat berdasarkan jarak yang dihitung sebelumnya.

Menentukan Kelas Prediksi:

  • Kelas mayoritas dari tetangga terdekat digunakan sebagai prediksi untuk titik data yang dipertanyakan.
heart<-read.csv("heart.csv")
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 ...
##  $ thalachh: 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 ...
##  $ thall   : int  1 2 2 2 2 1 2 3 3 2 ...
##  $ target  : int  1 1 1 1 1 1 1 1 1 1 ...
library(dplyr)
heart <- heart %>% 
  mutate_if(is.integer, as.numeric) %>% 
  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")))
glimpse(heart)
## Rows: 303
## Columns: 14
## $ age      <dbl> 63, 37, 41, 56, 57, 57, 56, 44, 52, 57, 54, 48, 49, 64, 58, 5…
## $ sex      <fct> Male, Male, Female, Male, Female, Male, Female, Male, Male, M…
## $ cp       <dbl> 3, 2, 1, 1, 0, 0, 1, 1, 2, 2, 0, 2, 1, 3, 3, 2, 2, 3, 0, 3, 0…
## $ trestbps <dbl> 145, 130, 130, 120, 120, 140, 140, 120, 172, 150, 140, 130, 1…
## $ chol     <dbl> 233, 250, 204, 236, 354, 192, 294, 263, 199, 168, 239, 275, 2…
## $ fbs      <fct> True, False, False, False, False, False, False, False, True, …
## $ restecg  <dbl> 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1…
## $ thalachh <dbl> 150, 187, 172, 178, 163, 148, 153, 173, 162, 174, 160, 139, 1…
## $ exang    <fct> No, No, No, No, Yes, No, No, No, No, No, No, No, No, Yes, No,…
## $ 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    <dbl> 0, 0, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 0, 2, 2, 1…
## $ ca       <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0…
## $ thall    <dbl> 1, 2, 2, 2, 2, 1, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3…
## $ target   <fct> Not Health, Not Health, Not Health, Not Health, Not Health, N…

Tipe data dari variabel respon sebaiknya berupa factor agar memudahkan pengukuran pada proses-proses selanjutnya.

Kode berikut menampilkan jumlah data dan distribusinya berdasarkan variabel target. Dari total 303 observasi, terdapat 138 dalam kelas “Health” dan 165 dalam kelas “Not Health”. Proporsi kedua kelas ini cukup seimbang, yaitu sekitar 45,55% untuk kelas “Health” dan 54,45% untuk kelas “Not Health”.

Melakukan pengecekan terhadap missing value

colSums(is.na(heart))
##      age      sex       cp trestbps     chol      fbs  restecg thalachh 
##        0        0        0        0        0        0        0        0 
##    exang  oldpeak    slope       ca    thall   target 
##        0        0        0        0        0        0
# Pre-Processing Data
prop.table(table(heart$target))
## 
##     Health Not Health 
##  0.4554455  0.5445545
table(heart$target)
## 
##     Health Not Health 
##        138        165

2 Splitting Train-Test

library(caret)
set.seed(100)  # mengatur seed tertentu untuk hasil yang dapat direproduksi

# data latih = 70%, data uji = 30%
trainIndex <- createDataPartition(heart$target, p = 0.70, list = FALSE, times = 1)

# Buat data latih dan data uji berdasarkan indeks yang dihasilkan
data.train <- heart[trainIndex, ]
data.test <- heart[-trainIndex, ]

# melihat komposisi setiap kelas pada data train dan test
cbind("train" = table(data.train$target), "test" = table(data.test$target))
##            train test
## Health        97   41
## Not Health   116   49

3 Features Scaling

KNN adalah model berbasis jarak, sehingga ukuran jarak menjadi faktor penting. Perbedaan skala antar fitur dapat memengaruhi kinerja model. Untuk mengatasi hal ini, disarankan untuk selalu melakukan scaling pada fitur, terutama ketika terdapat perbedaan skala yang signifikan. Salah satu metode scaling yang dapat digunakan adalah normalisasi (MinMax Scaling), yang mengubah setiap fitur menjadi rentang nilai antara 0 dan 1.

Menerapkan scaling untuk variabel tipe numerik, yaitu pada variabel 1, 3, 4, 5, 7, 8, 10, 11, 12, dan 13.

preproc.params <- preProcess(data.train[, c(1,3,4,5,7,8,10,11,12,13)], method = "range")

scaled.data.train <- predict(preproc.params, data.train[, c(1,3,4,5,7,8,10,11,12,13)])
scaled.data.test <- predict(preproc.params, data.test[, c(1,3,4,5,7,8,10,11,12,13)])

summary(scaled.data.train)
##       age               cp            trestbps           chol       
##  Min.   :0.0000   Min.   :0.0000   Min.   :0.0000   Min.   :0.0000  
##  1st Qu.:0.3250   1st Qu.:0.0000   1st Qu.:0.2453   1st Qu.:0.3025  
##  Median :0.5000   Median :0.3333   Median :0.3396   Median :0.4093  
##  Mean   :0.4954   Mean   :0.3114   Mean   :0.3480   Mean   :0.4254  
##  3rd Qu.:0.6500   3rd Qu.:0.6667   3rd Qu.:0.4340   3rd Qu.:0.5160  
##  Max.   :1.0000   Max.   :1.0000   Max.   :1.0000   Max.   :1.0000  
##     restecg         thalachh         oldpeak           slope       
##  Min.   :0.000   Min.   :0.0000   Min.   :0.0000   Min.   :0.0000  
##  1st Qu.:0.000   1st Qu.:0.4667   1st Qu.:0.0000   1st Qu.:0.5000  
##  Median :0.500   Median :0.6095   Median :0.1071   Median :0.5000  
##  Mean   :0.277   Mean   :0.5764   Mean   :0.1885   Mean   :0.7113  
##  3rd Qu.:0.500   3rd Qu.:0.7238   3rd Qu.:0.3214   3rd Qu.:1.0000  
##  Max.   :1.000   Max.   :1.0000   Max.   :1.0000   Max.   :1.0000  
##        ca             thall       
##  Min.   :0.0000   Min.   :0.0000  
##  1st Qu.:0.0000   1st Qu.:0.6667  
##  Median :0.0000   Median :0.6667  
##  Mean   :0.1702   Mean   :0.7793  
##  3rd Qu.:0.2500   3rd Qu.:1.0000  
##  Max.   :1.0000   Max.   :1.0000

4 KNN using ‘class’

Sebagai intuisi, dicoba dilakukan menggunakan k=5

library(class)
pred.knn <- knn(train=scaled.data.train, test=scaled.data.test, 
             cl=data.train$target, k=5)

Menampilkan hasil prediksi

print(pred.knn)
##  [1] Health     Not Health Health     Not Health Not Health Health    
##  [7] Not Health Not Health Not Health Not Health Not Health Not Health
## [13] Not Health Not Health Not Health Not Health Not Health Not Health
## [19] Not Health Not Health Not Health Not Health Not Health Not Health
## [25] Not Health Not Health Not Health Not Health Not Health Not Health
## [31] Not Health Not Health Not Health Not Health Not Health Not Health
## [37] Not Health Health     Not Health Health     Health     Not Health
## [43] Health     Not Health Health     Health     Not Health Not Health
## [49] Not Health Health     Not Health Health     Not Health Health    
## [55] Not Health Not Health Health     Health     Health     Not Health
## [61] Health     Health     Health     Not Health Not Health Health    
## [67] Not Health Health     Health     Not Health Health     Health    
## [73] Health     Health     Health     Not Health Health     Not Health
## [79] Not Health Health     Health     Health     Not Health Health    
## [85] Health     Not Health Health     Health     Health     Not Health
## Levels: Health Not Health

Evaluasi Model

Evaluasi hasil prediksi pada data uji

conf_matrix <- confusionMatrix(pred.knn, data.test$target)
print(conf_matrix)
## Confusion Matrix and Statistics
## 
##             Reference
## Prediction   Health Not Health
##   Health         26          9
##   Not Health     15         40
##                                           
##                Accuracy : 0.7333          
##                  95% CI : (0.6297, 0.8211)
##     No Information Rate : 0.5444          
##     P-Value [Acc > NIR] : 0.0001788       
##                                           
##                   Kappa : 0.4559          
##                                           
##  Mcnemar's Test P-Value : 0.3074342       
##                                           
##             Sensitivity : 0.6341          
##             Specificity : 0.8163          
##          Pos Pred Value : 0.7429          
##          Neg Pred Value : 0.7273          
##              Prevalence : 0.4556          
##          Detection Rate : 0.2889          
##    Detection Prevalence : 0.3889          
##       Balanced Accuracy : 0.7252          
##                                           
##        'Positive' Class : Health          
## 

Akurasi prediksi yang diperoleh adalah 0,7333, atau algoritma KNN berhasil memprediksi dengan benar 73,33% dari data uji. Berdasarkan setiap kelas, dari 138 data uji berkelas “Health,” 26 diprediksi dengan benar dan 15 salah, menghasilkan nilai sensitivitas (prediksi benar untuk kelas positif) sebesar 0,6341. Sementara itu, dari 165 data uji berkelas “Not Health,” 9 diprediksi dengan benar dan 40 salah (spesifisitas sebesar 0,8163).

5 KNN using ‘caret’

Library caret dapat menghadirkan fungsi train yang dapat dimanfaatkan untuk melakukan iterasi berbagai nilai k. Selain itu, dengan fungsi train dapat dilakukan validasi silang sehingga hasil yang diperoleh dapat dipercaya dan dapat mengurangi overfitting.

set.seed(100)  # mengatur seed tertentu untuk hasil yang dapat direproduksi

# proses pengukuran menggunakan k-fold cv dengan 5 fold
control = trainControl(method = "cv", number = 5)

# pencarian dilakukan untuk k=1,2,...30
grid = expand.grid(k = 1:30)

knn.grid <- train(x=scaled.data.train, 
                   y=data.train$target, 
                   method = "knn",
                   trControl = control,
                   tuneGrid = grid)

# Menampilkan hasil model
print(knn.grid)
## k-Nearest Neighbors 
## 
## 213 samples
##  10 predictor
##   2 classes: 'Health', 'Not Health' 
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold) 
## Summary of sample sizes: 170, 171, 170, 170, 171 
## Resampling results across tuning parameters:
## 
##   k   Accuracy   Kappa    
##    1  0.7562569  0.5080950
##    2  0.7560354  0.5073106
##    3  0.8221484  0.6402496
##    4  0.8217054  0.6383932
##    5  0.8263566  0.6469285
##    6  0.8263566  0.6468774
##    7  0.8404208  0.6751247
##    8  0.8217054  0.6366528
##    9  0.8170543  0.6258825
##   10  0.8124031  0.6166919
##   11  0.8263566  0.6448862
##   12  0.8077519  0.6055443
##   13  0.8170543  0.6245499
##   14  0.8076412  0.6059186
##   15  0.7889258  0.5648034
##   16  0.7888151  0.5652375
##   17  0.7984496  0.5840741
##   18  0.7795127  0.5454707
##   19  0.7889258  0.5653755
##   20  0.7841639  0.5552673
##   21  0.7888151  0.5650840
##   22  0.7982281  0.5845022
##   23  0.7888151  0.5652290
##   24  0.7887043  0.5650403
##   25  0.7887043  0.5643130
##   26  0.7887043  0.5643168
##   27  0.7792913  0.5458184
##   28  0.7794020  0.5453074
##   29  0.7794020  0.5460348
##   30  0.7792913  0.5459310
## 
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was k = 7.

Hasil di atas menunjukkan bahwa nilai k yang menghasilkan akurasi tertinggi dari validasi silang adalah k=7, dengan akurasi sebesar 0,8404208. Namun, perlu dicatat bahwa nilai ini berasal dari data latih. Oleh karena itu, performa model yang sesungguhnya perlu dievaluasi menggunakan data uji. Evaluasi pada data uji menunjukkan akurasi sebesar 0,7556, dengan nilai sensitivitas dan spesifisitas masing-masing sebesar 0,6341 dan 0,8571.

Line plot untuk melihat perubahan akurasi berdasarkan nilai K yang diuji.

ggplot(knn.grid$results, aes(x=k, y=Accuracy)) + 
  geom_line(color="darkblue") + 
  geom_point(color="red") +
  labs(title="Performa Akurasi berdasarkan Nilai K", x="K", y="Accuracy")

Evaluasi Model

Membuat prediksi untuk data uji

predictions <- predict(knn.grid, newdata = scaled.data.test)
conf_matrix <- confusionMatrix(predictions, data.test$target)
print(conf_matrix)
## Confusion Matrix and Statistics
## 
##             Reference
## Prediction   Health Not Health
##   Health         26          7
##   Not Health     15         42
##                                         
##                Accuracy : 0.7556        
##                  95% CI : (0.6536, 0.84)
##     No Information Rate : 0.5444        
##     P-Value [Acc > NIR] : 2.879e-05     
##                                         
##                   Kappa : 0.4992        
##                                         
##  Mcnemar's Test P-Value : 0.1356        
##                                         
##             Sensitivity : 0.6341        
##             Specificity : 0.8571        
##          Pos Pred Value : 0.7879        
##          Neg Pred Value : 0.7368        
##              Prevalence : 0.4556        
##          Detection Rate : 0.2889        
##    Detection Prevalence : 0.3667        
##       Balanced Accuracy : 0.7456        
##                                         
##        'Positive' Class : Health        
## 

Kurva ROC KKN

ROC (Receiver Operating Characteristic) adalah kurva yang menunjukkan kinerja model klasifikasi pada berbagai ambang batas probabilitas. ROC curve dibentuk dengan memplot True Positive Rate (TPR) terhadap False Positive Rate (FPR) pada berbagai nilai ambang (threshold), untuk melihat bagaimana model melakukan klasifikasi pada tingkat probabilitas yang berbeda.

ROC curve menunjukkan kinerja model pada berbagai ambang threshold. Dapat dilakukan pemilihan threshold spesifik yang memberikan keseimbangan optimal antara TPR dan FPR.

# Menggunakan prediksi probabilitas untuk ROC Curve
library(pROC)

# Prediksi probabilitas kelas "Not Health" untuk data uji
pred_probs <- predict(knn.grid, newdata = scaled.data.test, type = "prob")

# Mengonversi data.test$target ke numerik untuk ROC (1 untuk Not Health, 0 untuk Health)
data.test$target_numeric <- ifelse(data.test$target == "Not Health", 1, 0)

# Membuat objek ROC curve
rocknn <- roc(data.test$target_numeric, pred_probs[, "Not Health"])

# Plot ROC curve
plot(rocknn, main = "Kurva ROC untuk KNN", col = "red")

ROC curve membantu melihat kekuatan model dalam memisahkan kelas, dan AUC memberikan metrik untuk menilai kinerja secara keseluruhan.

  • AUC menunjukkan kemampuan model dalam memisahkan kedua kelas pada berbagai threshold. Semakin besar AUC (mendekati 1), semakin baik kinerja model.

  • Nilai AUC sekitar 0.7–0.8 umumnya dianggap sebagai performa yang moderat, sedangkan 0.8–0.9 menunjukkan performa yang baik, dan nilai di atas 0.9 berarti performa sangat baik.

  • AUC rendah (mendekati 0.5) menunjukkan model yang lemah atau tidak mampu membedakan kelas dengan baik.

auc_value <- auc(rocknn)
print(auc_value)
## Area under the curve: 0.8591

ROC curve pada plot memiliki AUC sekitar 0.8591, ini berarti model KNN memiliki performa baik dalam membedakan antara pasien “Health” dan “Not Health”.

Perbandingan data aktual dan prediksi

results <- data.frame(data.test[,-14],
  Actual = data.test$target,
  Predicted = predictions
)

library(DT)
datatable(results, 
          options = list(pageLength = 10,       # Menampilkan 10 baris per halaman
                         autoWidth = TRUE,      # Menyesuaikan lebar kolom secara otomatis
                         scrollY = "400px",     # Menetapkan tinggi scroll vertikal
                         scrollX = TRUE),       # Mengaktifkan scroll horizontal
          caption = 'Tabel: Nilai Aktual dan Prediksi')