1. INPUT DATA

Tujuan: Memuat dataset dan mengambil sampel acak representatif dari keseluruhan data.

  1. library(readxl) -> Memanggil package readxl yang memungkinkan R membaca file Excel (.xlsx). Package ini perlu diinstal terlebih dahulu jika belum ada.

  2. read_excel(…) -> Membaca file heart_attack_prediction_indonesia.xlsx dan menyimpannya ke objek Datajantung. Dataset ini berisi data pasien dari Indonesia dengan berbagai fitur klinis dan demografis terkait risiko serangan jantung.

  3. set.seed(…) -> Menetapkan “benih” angka acak agar hasil pengambilan sampel selalu konsisten setiap kali kode dijalankan ulang. Tanpa ini, sampel yang terpilih akan berbeda setiap kali dijalankan.

  4. sample(1:nrow(Datajantung), 9407) -> Membuat vektor indeks baris secara acak sebanyak 9.407 dari total baris dataset. 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.

  1. Data_sample <- Datajantung[sample_index, ] : Mengambil baris-baris yang indeksnya terpilih tadi, membentuk subset data yang akan digunakan dalam analisis.

Mengapa perlu sampling? Jika dataset asli sangat besar, pemodelan pada seluruh data bisa memakan waktu dan sumber daya komputasi yang besar. Sampling membantu efisiensi tanpa mengorbankan representativitas data secara signifikan.

library(readxl)
Datajantung <- read_excel("heart_attack_prediction_indonesia.xlsx")
set.seed(36)
sample_index <- sample(
  1:nrow(Datajantung),
  9407
)
Data_sample <- Datajantung[sample_index, ]

2. FAKTORKAN VARIABEL KATEGORI

Tujuan: Mengubah variabel-variabel kategorikal dari tipe karakter/numerik menjadi tipe factor.

Decision Tree membutuhkan variabel kategorikal dalam format factor agar bisa diproses dengan benar oleh R.

Sebanyak 18 variabel dikonversi, mulai dari gender, region, hypertension, hingga heart_attack sebagai variabel target.

Secara default, R mungkin membaca variabel seperti hypertension (bernilai 0/1) sebagai numerik, bukan kategori. Jika dibiarkan numerik, algoritma rpart akan memperlakukannya sebagai data kontinu dan mencari titik split angka, bukan sebagai dua kategori berbeda (Ya/Tidak). Konversi ke factor memastikan algoritma memahami konteks semantik variabel tersebut.

library(caret)
## Warning: package 'caret' was built under R version 4.5.3
## Loading required package: ggplot2
## Loading required package: lattice
library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.5.3
## Warning: package 'lubridate' was built under R version 4.5.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.2.0     ✔ readr     2.1.6
## ✔ forcats   1.0.1     ✔ stringr   1.6.0
## ✔ lubridate 1.9.5     ✔ tibble    3.3.1
## ✔ purrr     1.2.1     ✔ tidyr     1.3.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ✖ purrr::lift()   masks caret::lift()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(dplyr)

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),
         smoking_status = factor(smoking_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)
  )

3. TENTUKAN VARIABEL TARGET (Y) DAN VARIABEL FITUR (X) DARI DATA YANG SUDAH DIKONVERSI FAKTOR

Tujuan: Memisahkan variabel dependen (target) dan variabel independen (fitur).

y berisi label kelas (heart_attack: 0 atau 1), sedangkan X berisi semua fitur prediktor.

Pemisahan ini merupakan langkah standar dalam persiapan supervised learning. Penggunaan dplyr::select() dengan prefix namespace dplyr:: dilakukan untuk menghindari konflik nama fungsi, karena beberapa package lain (seperti MASS) juga memiliki fungsi select(). Ini adalah praktik pemrograman yang baik dalam R.

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

4. BUAT PARTISI DATA UNTUK TRAINING DAN TESTING

Tujuan: Membagi data menjadi dua subset -> training (80%) dan testing (20%) -> untuk melatih dan mengevaluasi model secara objektif.

Mengapa 80:20? Ini adalah rasio yang umum digunakan dalam machine learning. Data training yang besar memberi model cukup informasi untuk belajar, sementara data testing yang memadai memberikan estimasi performa yang cukup stabil.

Stratified Partitioning: createDataPartition() dari package caret membagi data secara stratified, artinya proporsi kelas heart_attack (0 dan 1) dijaga sama antara data training dan testing. Ini sangat penting ketika distribusi kelas tidak seimbang -> tanpa stratifikasi, bisa terjadi data testing hanya berisi satu kelas saja.

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

Pisahkan data menjadi set training dan testing

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

5. VERIFIKASI TIPE DATA SETELAH KONVERSI

Tujuan: Melakukan validasi visual terhadap struktur data setelah preprocessing untuk memastikan tidak ada kesalahan konversi tipe data.

glimpse() menampilkan untuk setiap kolom: nama variabel, tipe data ( untuk factor, untuk numerik, dsb.), dan beberapa nilai pertama sebagai contoh.

Yang perlu diverifikasi:

  1. Semua 18 variabel kategorikal sudah bertipe

  2. Variabel numerik seperti age, cholesterol_level, bmi tetap bertipe atau

  3. Tidak ada kolom yang hilang (missing) secara tidak sengaja

message("Struktur data training setelah konversi:")
## Struktur data training setelah konversi:
glimpse(train_data)
## Rows: 7,526
## Columns: 28
## $ age                            <dbl> 64, 44, 36, 75, 57, 67, 66, 59, 34, 38,…
## $ gender                         <fct> Male, Female, Female, Male, Male, Femal…
## $ region                         <fct> Urban, Urban, Urban, Urban, Urban, Rura…
## $ income_level                   <fct> Low, Middle, Low, Middle, High, Low, Mi…
## $ hypertension                   <fct> 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, …
## $ diabetes                       <fct> 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ cholesterol_level              <dbl> 261, 230, 292, 217, 193, 252, 100, 258,…
## $ obesity                        <fct> 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, …
## $ waist_circumference            <dbl> 105, 108, 93, 100, 117, 85, 80, 71, 80,…
## $ family_history                 <fct> 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, …
## $ smoking_status                 <fct> Past, Never, Never, Never, Never, Never…
## $ alcohol_consumption            <fct> None, None, None, None, None, Moderate,…
## $ physical_activity              <fct> Moderate, Low, Moderate, Low, Moderate,…
## $ dietary_habits                 <fct> Unhealthy, Unhealthy, Healthy, Unhealth…
## $ air_pollution_exposure         <fct> Low, Low, Moderate, Moderate, High, Mod…
## $ 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, 129, 114, 137, 117,…
## $ blood_pressure_diastolic       <dbl> 74, 93, 76, 98, 87, 76, 101, 95, 90, 67…
## $ fasting_blood_sugar            <dbl> 116, 111, 84, 147, 176, 70, 131, 75, 75…
## $ cholesterol_hdl                <dbl> 46, 66, 44, 46, 40, 46, 41, 48, 51, 59,…
## $ cholesterol_ldl                <dbl> 185, 121, 107, 156, 109, 107, 107, 131,…
## $ triglycerides                  <dbl> 145, 243, 140, 192, 223, 193, 168, 118,…
## $ EKG_results                    <fct> Abnormal, Normal, Normal, Normal, Norma…
## $ previous_heart_disease         <fct> 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, …
## $ medication_usage               <fct> 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, …
## $ participated_in_free_screening <fct> 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, …
## $ heart_attack                   <fct> 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, …
message("\nStruktur data testing setelah konversi:")
## 
## Struktur data testing setelah konversi:
glimpse(test_data)
## Rows: 1,881
## Columns: 28
## $ age                            <dbl> 46, 49, 72, 47, 70, 41, 51, 47, 63, 60,…
## $ gender                         <fct> Female, Female, Male, Male, Male, Male,…
## $ region                         <fct> Urban, Urban, Rural, Urban, Urban, Rura…
## $ income_level                   <fct> Low, Middle, Middle, High, High, Low, H…
## $ hypertension                   <fct> 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, …
## $ diabetes                       <fct> 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, …
## $ cholesterol_level              <dbl> 199, 165, 243, 148, 253, 189, 205, 174,…
## $ obesity                        <fct> 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, …
## $ waist_circumference            <dbl> 80, 115, 91, 84, 124, 56, 100, 103, 99,…
## $ family_history                 <fct> 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, …
## $ smoking_status                 <fct> Never, Past, Never, Current, Never, Nev…
## $ alcohol_consumption            <fct> High, None, None, Moderate, None, Moder…
## $ physical_activity              <fct> Low, Low, Moderate, Moderate, Moderate,…
## $ dietary_habits                 <fct> Healthy, Healthy, Unhealthy, Healthy, U…
## $ air_pollution_exposure         <fct> Moderate, Low, Moderate, Moderate, High…
## $ stress_level                   <fct> Moderate, Moderate, Low, Moderate, Low,…
## $ sleep_hours                    <dbl> 9.000000, 9.000000, 3.000000, 7.998115,…
## $ blood_pressure_systolic        <dbl> 148, 145, 120, 132, 130, 117, 127, 119,…
## $ blood_pressure_diastolic       <dbl> 82, 88, 75, 92, 70, 84, 71, 81, 79, 74,…
## $ fasting_blood_sugar            <dbl> 85, 138, 96, 117, 103, 175, 83, 137, 10…
## $ cholesterol_hdl                <dbl> 51, 46, 45, 34, 47, 56, 62, 53, 52, 57,…
## $ cholesterol_ldl                <dbl> 176, 70, 50, 167, 157, 71, 123, 130, 14…
## $ triglycerides                  <dbl> 107, 178, 125, 188, 204, 189, 75, 146, …
## $ EKG_results                    <fct> Normal, Normal, Normal, Abnormal, Norma…
## $ previous_heart_disease         <fct> 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, …
## $ medication_usage               <fct> 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, …
## $ participated_in_free_screening <fct> 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, …
## $ heart_attack                   <fct> 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, …

6. PROPORSI HEART ATTACK

Tujuan: Mengecek apakah distribusi kelas target seimbang antara data training dan testing, sekaligus mendeteksi potensi class imbalance.

Jika proporsi kelas sangat tidak seimbang (misalnya 95% “tidak serangan” dan 5% “serangan”), model bisa menjadi bias. Jika proporsi kelas 0 dan 1 mendekati sama di keduanya -> stratified split berhasil, tidak perlu penanganan khusus.

ini memastikan distribusi kelas serupa antara training dan testing -> bukti bahwa stratified split berhasil.

  1. Proporsi heart_attack di data training
message("Proporsi 'heart_attack' di data training:")
## Proporsi 'heart_attack' di data training:
prop_train <- prop.table(table(train_data$heart_attack))
print(prop_train)
## 
##         0         1 
## 0.5932766 0.4067234
  1. proporsi status Nicm di data testing
message("\nProporsi 'heart_attack' di data testing:")
## 
## Proporsi 'heart_attack' di data testing:
prop_test <- prop.table(table(test_data$heart_attack))
print(prop_test)
## 
##         0         1 
## 0.5933014 0.4066986

7. MENGATUR PARAMETER KONTROL UNTUK PELATIHAN MODEL DENGAN MELAKUKAN 5-FOLD CROSS-VALIDATION

Tujuan: Menyiapkan konfigurasi validasi silang (cross-validation) untuk evaluasi model yang lebih robust.

Cara kerja 5-Fold Cross-Validation -> Data training dibagi menjadi 5 bagian (fold) yang sama besar.

Model dilatih sebanyak 5 kali:

  1. Iterasi 1: Fold 2-5 untuk training, Fold 1 untuk validasi

  2. Iterasi 2: Fold 1, 3-5 untuk training, Fold 2 untuk validasi

  3. … dan seterusnya

Akurasi akhir adalah rata-rata dari 5 iterasi tersebut, menghasilkan estimasi performa yang lebih stabil dan tidak bergantung pada satu pembagian data saja.

Ini menghasilkan estimasi performa model yang lebih stabil dan tidak overfitting.

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

8. DESICION TREE

Tujuan: Mendefinisikan rentang nilai hyperparameter yang akan diuji secara sistematis melalui Grid Search.

Penjelasan hyperparameter:

  1. cp (Complexity Parameter): Mengontrol seberapa agresif pruning (pemangkasan cabang) dilakukan pada pohon. Nilai kecil (0.001) membiarkan pohon tumbuh lebih dalam dan kompleks -> risiko overfitting. Nilai besar (0.1) memangkas banyak cabang -> pohon lebih sederhana, risiko underfitting.

  2. minsplit: Jumlah minimum observasi yang harus ada di suatu node agar node tersebut boleh dipecah menjadi dua cabang. Nilai kecil (10) memungkinkan split bahkan pada node yang sedikit datanya -> pohon lebih detail. Nilai besar (30) membuat pohon lebih konservatif.

  3. maxdepth: Kedalaman maksimum pohon dari root (akar) ke leaf (daun). Nilai 3 menghasilkan pohon sangat sederhana (mudah diinterpretasi), nilai 7 menghasilkan pohon yang lebih kompleks dan berpotensi lebih akurat namun sulit dibaca.

Total kombinasi yang diuji: 3 × 3 × 3 = 27 model

library(rpart)
library(caret)

set.seed(36)

results <- data.frame()

cp_list <- c(0.001, 0.01, 0.1)
minsplit_list <- c(10, 20, 30)
maxdepth_list <- c(3, 5, 7)

9. MODEL, PREDIKSI, DAN EVALUASI

Tujuan: Melakukan Grid Search secara manual -> melatih dan mengevaluasi 27 model Decision Tree dengan kombinasi hyperparameter yang berbeda, lalu merekam akurasi masing-masing.

Alur kerja iterasi:

  1. Bangun model rpart dengan kombinasi cp, minsplit, maxdepth saat itu

  2. Prediksi label kelas pada test_data

  3. Evaluasi dengan confusionMatrix() dan ekstrak nilai akurasi

  4. Simpan kombinasi parameter beserta akurasinya ke dataframe results

rbind(results, …) menambahkan baris baru ke dataframe results di setiap iterasi, sehingga pada akhir loop results berisi 27 baris -> satu per kombinasi hyperparameter.

Hasil setiap kombinasi disimpan ke dataframe results untuk dibandingkan.

for (cp in cp_list) {
  for (minsplit in minsplit_list) {
    for (maxdepth in maxdepth_list) {
      
      # Model
      model_loop <- rpart(
        heart_attack ~ .,
        data = train_data,
        method = "class",
        control = rpart.control(
          cp = cp,
          minsplit = minsplit,
          maxdepth = maxdepth
        )
      )
      
      # Prediksi
      pred <- predict(
        model_loop,
        newdata = test_data,
        type = "class"
      )
      
      # Evaluasi
      cm <- confusionMatrix(pred, test_data$heart_attack)
      acc <- cm$overall["Accuracy"]
      
      # Simpan hasil
      results <- rbind(results, data.frame(
        cp = cp,
        minsplit = minsplit,
        maxdepth = maxdepth,
        accuracy = acc
      ))
    }
  }
}

Tujuan: Menampilkan tabel lengkap berisi 27 kombinasi hyperparameter beserta akurasi masing-masing untuk perbandingan.

Tabel ini memudahkan analisis pola: misalnya, apakah maxdepth yang lebih besar selalu menghasilkan akurasi lebih tinggi? Atau apakah cp kecil justru menurunkan performa karena overfitting?

print(results)
##               cp minsplit maxdepth  accuracy
## Accuracy   0.001       10        3 0.6916534
## Accuracy1  0.001       10        5 0.7166401
## Accuracy2  0.001       10        7 0.7256778
## Accuracy3  0.001       20        3 0.6916534
## Accuracy4  0.001       20        5 0.7166401
## Accuracy5  0.001       20        7 0.7256778
## Accuracy6  0.001       30        3 0.6916534
## Accuracy7  0.001       30        5 0.7166401
## Accuracy8  0.001       30        7 0.7246146
## Accuracy9  0.010       10        3 0.6916534
## Accuracy10 0.010       10        5 0.7129187
## Accuracy11 0.010       10        7 0.7129187
## Accuracy12 0.010       20        3 0.6916534
## Accuracy13 0.010       20        5 0.7129187
## Accuracy14 0.010       20        7 0.7129187
## Accuracy15 0.010       30        3 0.6916534
## Accuracy16 0.010       30        5 0.7129187
## Accuracy17 0.010       30        7 0.7129187
## Accuracy18 0.100       10        3 0.6549708
## Accuracy19 0.100       10        5 0.6549708
## Accuracy20 0.100       10        7 0.6549708
## Accuracy21 0.100       20        3 0.6549708
## Accuracy22 0.100       20        5 0.6549708
## Accuracy23 0.100       20        7 0.6549708
## Accuracy24 0.100       30        3 0.6549708
## Accuracy25 0.100       30        5 0.6549708
## Accuracy26 0.100       30        7 0.6549708

10. MODEL TERBAIK

Tujuan: Secara otomatis memilih kombinasi hyperparameter yang menghasilkan akurasi tertinggi pada data testing.

  1. which.max(results$accuracy) -> mengembalikan nomor baris (indeks) dengan nilai akurasi tertinggi

  2. results[indeks, ] -> mengambil seluruh baris tersebut, berisi nilai cp, minsplit, maxdepth, dan accuracy

Kombinasi terpilih inilah yang dianggap sebagai konfigurasi optimal dan akan digunakan untuk membangun model final.

best_param <- results[which.max(results$accuracy), ]
cat("\nPARAMETER TERBAIK:\n")
## 
## PARAMETER TERBAIK:
print(best_param)
##              cp minsplit maxdepth  accuracy
## Accuracy2 0.001       10        7 0.7256778

11. BANGUN MODEL DENGAN PARAMETER TERBAIK

Tujuan: Melatih ulang model Decision Tree menggunakan seluruh data training dengan hyperparameter terbaik hasil Grid Search.

Penjelasan komponen:

  1. heart_attack ~ . -> Formula: prediksi heart_attack menggunakan semua variabel lainnya (. berarti semua kolom)

  2. method = “class” -> Menentukan ini adalah masalah klasifikasi, bukan regresi

  3. rpart.control(…) -> Menetapkan hyperparameter optimal dari best_param

Model ini (dt_modelT) adalah model final yang siap dievaluasi dan diinterpretasi.

dt_modelT <- rpart(
  heart_attack ~ .,
  data = train_data,
  method = "class",
  control = rpart.control(
    cp = best_param$cp,
    minsplit = best_param$minsplit,
    maxdepth = best_param$maxdepth
  )
)

12. PREDIKSI

Tujuan: Mengaplikasikan model final ke data testing untuk menghasilkan prediksi kelas.

  1. newdata = test_data -> Model memprediksi baris-baris yang belum pernah “dilihat” selama training

  2. type = “class” -> Output berupa label kelas (0 atau 1), bukan probabilitas. Jika ingin probabilitas, gunakan type = “prob”

Hasil pred_dtT adalah vektor faktor berisi prediksi untuk setiap baris di test_data.

pred_dtT <- predict(dt_modelT, newdata = test_data, type = "class")

13. EVALUASI MODEL

Tujuan: Mengukur performa model final secara komprehensif.

Confusion Matrix menghasilkan metrik seperti:

  1. Accuracy -> proporsi prediksi benar secara keseluruhan

  2. Sensitivity (Recall) -> kemampuan model mendeteksi kasus serangan jantung (kelas positif = 1)

  3. Specificity -> kemampuan model mengidentifikasi yang tidak serangan jantung

  4. Kappa -> akurasi yang dikoreksi terhadap peluang acak

message("\nConfusion Matrix DT:")
## 
## Confusion Matrix DT:
confusionMatrix(
  pred_dtT,
  test_data$heart_attack,
  positive = "1"
)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1
##          0 893 293
##          1 223 472
##                                           
##                Accuracy : 0.7257          
##                  95% CI : (0.7049, 0.7457)
##     No Information Rate : 0.5933          
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.4233          
##                                           
##  Mcnemar's Test P-Value : 0.002385        
##                                           
##             Sensitivity : 0.6170          
##             Specificity : 0.8002          
##          Pos Pred Value : 0.6791          
##          Neg Pred Value : 0.7530          
##              Prevalence : 0.4067          
##          Detection Rate : 0.2509          
##    Detection Prevalence : 0.3695          
##       Balanced Accuracy : 0.7086          
##                                           
##        'Positive' Class : 1               
## 

14. VISUALISASI POHON KEPUTUSAN

Tujuan: Menampilkan struktur pohon keputusan secara visual.

  1. extra = 111 menampilkan probabilitas kelas, proporsi kelas, dan jumlah observasi di setiap node.

  2. fallen.leaves = TRUE menempatkan semua daun (leaf node) di baris bawah untuk keterbacaan.

  3. cex = 0.6 mengecilkan ukuran teks agar pohon tidak terlalu padat.

Visualisasi ini memungkinkan interpretasi faktor-faktor apa yang paling berpengaruh terhadap prediksi serangan jantung.

message("\nVisualisasi Decision Tree:")
## 
## Visualisasi Decision Tree:
# rpart.plot(dt_modelT, extra = 111, fallen.leaves = TRUE, cex = 0.6)