1. Library

Berikut adalah package yang digunakan dalam analisis ini. Package caret digunakan untuk proses pemodelan dan evaluasi, tidyverse dan dplyr untuk manipulasi data, serta e1071 sebagai dependensi algoritma klasifikasi.

library(readxl)
library(caret)
library(tidyverse)
library(dplyr)
library(e1071)

2. Import Data

Dataset yang digunakan adalah “Heart Attack Prediction in Indonesia” yang bersumber dari platform Kaggle. Dataset ini terdiri dari 158.355 data rekam kesehatan individu dengan 27 variabel independen dan 1 variabel dependen berupa status serangan jantung (heart_attack) dengan kategori positif (1) dan negatif (0).

Datajantung <- read_excel("C:/Users/User/Downloads/heart_attack_prediction_indonesia.xlsx"
)

cat("Dimensi dataset:", nrow(Datajantung), "baris dan", ncol(Datajantung), "kolom\n")
## Dimensi dataset: 158355 baris dan 28 kolom
head(Datajantung)
## # A tibble: 6 × 28
##     age gender region income_level hypertension diabetes cholesterol_level
##   <dbl> <chr>  <chr>  <chr>               <dbl>    <dbl>             <dbl>
## 1    60 Male   Rural  Middle                  0        1               211
## 2    53 Female Urban  Low                     0        0               208
## 3    62 Female Urban  Low                     0        0               231
## 4    73 Male   Urban  Low                     1        0               202
## 5    52 Male   Urban  Middle                  1        0               232
## 6    52 Male   Urban  Middle                  0        0               231
## # ℹ 21 more variables: obesity <dbl>, waist_circumference <dbl>,
## #   family_history <dbl>, smooking_status <chr>, alcohol_consumption <chr>,
## #   physical_activity <chr>, dietary_habits <chr>,
## #   air_pollution_exposure <chr>, stress_level <chr>, sleep_hours <dbl>,
## #   blood_pressure_systolic <dbl>, blood_pressure_diastolic <dbl>,
## #   fasting_blood_sugar <dbl>, cholesterol_hdl <dbl>, cholesterol_ldl <dbl>,
## #   triglycerides <dbl>, EKG_results <chr>, previous_heart_disease <dbl>, …

3. Sampling Data

Mengingat jumlah data yang sangat besar (158.355 data) sehingga memerlukan komputasi yang berat, maka dilakukan pengambilan sampel menggunakan rumus Slovin dengan tingkat kesalahan sebesar 1% sebagai berikut:

\[n = \frac{N}{1 + N(e)^2} = \frac{158.355}{1 + 158.355(0,01)^2} = \frac{158.355}{16,8355} \approx 9.407 \text{ data}\]

Berdasarkan perhitungan tersebut, diperoleh jumlah sampel sebanyak 9.407 data yang diambil secara acak (random sampling) dari keseluruhan dataset untuk memastikan sampel yang diperoleh bersifat representatif terhadap populasi data.

set.seed(36)
sample_index <- sample(
  1:nrow(Datajantung),
  9407
)
Data_sample <- Datajantung[sample_index, ]

cat("Jumlah sampel:", nrow(Data_sample), "\n")
## Jumlah sampel: 9407

4. Pre-Processing Data

4.1 Konversi Variabel ke Factor

Seluruh variabel kategorikal dikonversi ke dalam tipe data factor agar dapat diproses dengan benar oleh algoritma klasifikasi. Variabel yang dikonversi meliputi variabel demografis, klinis, gaya hidup, serta variabel target heart_attack.

selected_data_preprocessed_factors <- Data_sample %>%
  mutate(
    gender                         = factor(gender),
    region                         = factor(region),
    income_level                   = factor(income_level),
    hypertension                   = factor(hypertension),
    diabetes                       = factor(diabetes),
    obesity                        = factor(obesity),
    family_history                 = factor(family_history),
    smooking_status                = factor(smooking_status),
    alcohol_consumption            = factor(alcohol_consumption),
    physical_activity              = factor(physical_activity),
    dietary_habits                 = factor(dietary_habits),
    air_pollution_exposure         = factor(air_pollution_exposure),
    stress_level                   = factor(stress_level),
    EKG_results                    = factor(EKG_results),
    previous_heart_disease         = factor(previous_heart_disease),
    medication_usage               = factor(medication_usage),
    participated_in_free_screening = factor(participated_in_free_screening),
    heart_attack                   = factor(heart_attack)
  )

glimpse(selected_data_preprocessed_factors)
## Rows: 9,407
## Columns: 28
## $ age                            <dbl> 64, 44, 36, 75, 46, 49, 57, 72, 67, 47,…
## $ gender                         <fct> Male, Female, Female, Male, Female, Fem…
## $ region                         <fct> Urban, Urban, Urban, Urban, Urban, Urba…
## $ income_level                   <fct> Low, Middle, Low, Middle, Low, Middle, …
## $ hypertension                   <fct> 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, …
## $ diabetes                       <fct> 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ cholesterol_level              <dbl> 261, 230, 292, 217, 199, 165, 193, 243,…
## $ obesity                        <fct> 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, …
## $ waist_circumference            <dbl> 105, 108, 93, 100, 80, 115, 117, 91, 85…
## $ family_history                 <fct> 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, …
## $ smooking_status                <fct> Past, Never, Never, Never, Never, Past,…
## $ alcohol_consumption            <fct> None, None, None, None, High, None, Non…
## $ physical_activity              <fct> Moderate, Low, Moderate, Low, Low, Low,…
## $ dietary_habits                 <fct> Unhealthy, Unhealthy, Healthy, Unhealth…
## $ air_pollution_exposure         <fct> Low, Low, Moderate, Moderate, Moderate,…
## $ stress_level                   <fct> Moderate, Moderate, Moderate, Moderate,…
## $ sleep_hours                    <dbl> 5.504516, 6.668030, 8.058486, 7.335143,…
## $ blood_pressure_systolic        <dbl> 125, 138, 114, 130, 148, 145, 129, 120,…
## $ blood_pressure_diastolic       <dbl> 74, 93, 76, 98, 82, 88, 87, 75, 76, 92,…
## $ fasting_blood_sugar            <dbl> 116, 111, 84, 147, 85, 138, 176, 96, 70…
## $ cholesterol_hdl                <dbl> 46, 66, 44, 46, 51, 46, 40, 45, 46, 34,…
## $ cholesterol_ldl                <dbl> 185, 121, 107, 156, 176, 70, 109, 50, 1…
## $ triglycerides                  <dbl> 145, 243, 140, 192, 107, 178, 223, 125,…
## $ EKG_results                    <fct> Abnormal, Normal, Normal, Normal, Norma…
## $ previous_heart_disease         <fct> 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, …
## $ medication_usage               <fct> 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, …
## $ participated_in_free_screening <fct> 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, …
## $ heart_attack                   <fct> 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, …

4.2 Definisi Variabel Target dan Fitur

Variabel target (dependent variable) yang digunakan adalah heart_attack, sedangkan seluruh variabel lainnya digunakan sebagai fitur (independent variable) dalam proses pemodelan.

y <- selected_data_preprocessed_factors$heart_attack
X <- selected_data_preprocessed_factors %>%
  dplyr::select(-heart_attack)

cat("Jumlah fitur:", ncol(X), "\n")
## Jumlah fitur: 27
cat("Jumlah data :", nrow(X), "\n")
## Jumlah data : 9407

4.3 Split Data Training dan Testing (80:20)

Dataset dibagi menjadi dua bagian yang saling independen, yaitu 80% data training untuk membangun model dan 20% data testing untuk mengevaluasi performa model. Pembagian ini bertujuan untuk mengurangi potensi bias serta menghindari terjadinya overfitting pada saat evaluasi model.

set.seed(36)
train_index <- createDataPartition(
  y,
  p = 0.8,
  list = FALSE
)

train_data <- selected_data_preprocessed_factors[train_index, ]
test_data  <- selected_data_preprocessed_factors[-train_index, ]

cat("Jumlah data training:", nrow(train_data), "\n")
## Jumlah data training: 7526
cat("Jumlah data testing :", nrow(test_data), "\n")
## Jumlah data testing : 1881

4.4 Proporsi Kelas Target

Pemeriksaan proporsi kelas target dilakukan untuk memastikan distribusi kelas pada data training dan testing cukup seimbang, sehingga model tidak condong (bias) terhadap salah satu kelas.

cat("Proporsi heart_attack pada data training:\n")
## Proporsi heart_attack pada data training:
prop.table(table(train_data$heart_attack))
## 
##         0         1 
## 0.5932766 0.4067234
cat("\nProporsi heart_attack pada data testing:\n")
## 
## Proporsi heart_attack pada data testing:
prop.table(table(test_data$heart_attack))
## 
##         0         1 
## 0.5933014 0.4066986

5. Pemodelan KNN

5.1 Konfigurasi Cross Validation

Metode validasi yang digunakan adalah 5-Fold Cross Validation, yaitu dataset training dibagi menjadi 5 bagian (fold) secara bergantian sebagai data validasi, sementara 4 bagian lainnya digunakan sebagai data latih. Metode ini bertujuan untuk menghasilkan evaluasi model yang lebih stabil dan konsisten serta meminimalkan kemungkinan terjadinya overfitting.

train_control <- trainControl(
  method = "cv",
  number = 5
)

5.2 Training Model dengan Grid Search CV

Model KNN dilatih menggunakan Grid Search Cross Validation untuk mencari nilai K yang paling optimal. Rentang nilai K yang diuji adalah K = 1 hingga K = 97 dengan interval 2 (bilangan ganjil) guna menghindari hasil tie dalam proses voting. Selain itu, fitur dinormalisasi terlebih dahulu menggunakan center dan scale agar perbedaan skala antar variabel tidak memengaruhi perhitungan jarak pada algoritma KNN.

set.seed(36)
knn_model <- train(
  heart_attack ~ .,
  data       = train_data,
  method     = "knn",
  preProcess = c("center", "scale"),
  trControl  = train_control,
  tuneGrid   = expand.grid(
    k = seq(1, 97, by = 2)
  )
)

print(knn_model)
## k-Nearest Neighbors 
## 
## 7526 samples
##   27 predictor
##    2 classes: '0', '1' 
## 
## Pre-processing: centered (33), scaled (33) 
## Resampling: Cross-Validated (5 fold) 
## Summary of sample sizes: 6021, 6021, 6020, 6021, 6021 
## Resampling results across tuning parameters:
## 
##   k   Accuracy   Kappa    
##    1  0.6163949  0.1840840
##    3  0.6371242  0.2099754
##    5  0.6533340  0.2355387
##    7  0.6621034  0.2489196
##    9  0.6671533  0.2545688
##   11  0.6680833  0.2541828
##   13  0.6716707  0.2582010
##   15  0.6735313  0.2594258
##   17  0.6761893  0.2656316
##   19  0.6749942  0.2596409
##   21  0.6737987  0.2533051
##   23  0.6748613  0.2546526
##   25  0.6722036  0.2470049
##   27  0.6760565  0.2546347
##   29  0.6722032  0.2438988
##   31  0.6707423  0.2393650
##   33  0.6706096  0.2368217
##   35  0.6714063  0.2372753
##   37  0.6691473  0.2318988
##   39  0.6696789  0.2325027
##   41  0.6695460  0.2318959
##   43  0.6688819  0.2278814
##   45  0.6699444  0.2300803
##   47  0.6688819  0.2260009
##   49  0.6694140  0.2272251
##   51  0.6660920  0.2186238
##   53  0.6646298  0.2146982
##   55  0.6651616  0.2154783
##   57  0.6638330  0.2108359
##   59  0.6633009  0.2087614
##   61  0.6651616  0.2130432
##   63  0.6638327  0.2089083
##   65  0.6629025  0.2061147
##   67  0.6613080  0.2016227
##   69  0.6619725  0.2034154
##   71  0.6625038  0.2041127
##   73  0.6601122  0.1976280
##   75  0.6599793  0.1975475
##   77  0.6585174  0.1931067
##   79  0.6567904  0.1883111
##   81  0.6569232  0.1893422
##   83  0.6561259  0.1869753
##   85  0.6578535  0.1906366
##   87  0.6569232  0.1881064
##   89  0.6565246  0.1866398
##   91  0.6573216  0.1886156
##   93  0.6571889  0.1883530
##   95  0.6554614  0.1838239
##   97  0.6555943  0.1837132
## 
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was k = 17.

5.3 Nilai K Terbaik

Nilai K terbaik dipilih berdasarkan akurasi tertinggi yang diperoleh selama proses Grid Search Cross Validation. Nilai K inilah yang digunakan sebagai parameter final model KNN dalam proses prediksi.

cat("Nilai K terbaik:\n")
## Nilai K terbaik:
print(knn_model$bestTune)
##    k
## 9 17

5.4 Plot Hasil Tuning

Grafik berikut menunjukkan hubungan antara nilai K dan akurasi model selama proses hyperparameter tuning. Nilai K optimal ditandai dengan titik akurasi tertinggi pada grafik.

plot(
  knn_model,
  main = "Akurasi KNN berdasarkan Nilai K",
  xlab = "Jumlah Tetangga (K)",
  ylab = "Akurasi (Cross Validation)"
)


6. Evaluasi Model

6.1 Prediksi Data Testing

Setelah model KNN terbaik diperoleh, dilakukan prediksi terhadap data testing yang belum pernah dilihat oleh model sebelumnya. Hasil prediksi ini kemudian dibandingkan dengan label sebenarnya untuk mengukur performa model secara objektif.

knn_predictions <- predict(
  knn_model,
  newdata = test_data
)

6.2 Confusion Matrix

Confusion Matrix digunakan untuk mengevaluasi performa model secara menyeluruh dengan membandingkan hasil prediksi terhadap nilai aktual. Kelas positif yang digunakan adalah 1 yang merepresentasikan individu dengan risiko serangan jantung.

cm <- confusionMatrix(
  knn_predictions,
  test_data$heart_attack,
  positive = "1"
)

print(cm)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction    0    1
##          0 1040  529
##          1   76  236
##                                           
##                Accuracy : 0.6784          
##                  95% CI : (0.6567, 0.6994)
##     No Information Rate : 0.5933          
##     P-Value [Acc > NIR] : 1.609e-14       
##                                           
##                   Kappa : 0.2651          
##                                           
##  Mcnemar's Test P-Value : < 2.2e-16       
##                                           
##             Sensitivity : 0.3085          
##             Specificity : 0.9319          
##          Pos Pred Value : 0.7564          
##          Neg Pred Value : 0.6628          
##              Prevalence : 0.4067          
##          Detection Rate : 0.1255          
##    Detection Prevalence : 0.1659          
##       Balanced Accuracy : 0.6202          
##                                           
##        'Positive' Class : 1               
## 

6.3 Ringkasan Performa Model

Berikut adalah ringkasan metrik evaluasi model KNN yang digunakan dalam penelitian ini:

  • Accuracy : Proporsi keseluruhan prediksi yang benar terhadap total data uji
  • Precision : Proporsi prediksi positif yang benar-benar merupakan kasus positif
  • Recall : Kemampuan model dalam mendeteksi seluruh kasus positif yang sesungguhnya
  • F1-Score : Rata-rata harmonik antara precision dan recall
cat("==============================\n")
## ==============================
cat("  Ringkasan Performa Model KNN\n")
##   Ringkasan Performa Model KNN
cat("==============================\n")
## ==============================
cat("Accuracy :", round(cm$overall["Accuracy"] * 100, 2), "%\n")
## Accuracy : 67.84 %
cat("Precision:", round(cm$byClass["Precision"] * 100, 2), "%\n")
## Precision: 75.64 %
cat("Recall   :", round(cm$byClass["Recall"] * 100, 2), "%\n")
## Recall   : 30.85 %
cat("F1-Score :", round(cm$byClass["F1"] * 100, 2), "%\n")
## F1-Score : 43.83 %
cat("==============================\n")
## ==============================