1 Pendahuluan

Laporan ini membahas analisis klasifikasi menggunakan dataset Heart dengan tujuan lakukan klasifikasi pasien berdasarkan adanya penyakit jantung. Dalam laporan ini, model Decision Treedan Naive Bayes digunakan untuk memprediksi keberadaan penyakit jantung pada pasien.

1.1 Latar Belakang

Deteksi dini penyakit jantung sangat penting dalam upaya pencegahan dan pengobatan yang efektif. Metode klasifikasi menjadi alat yang berguna dalam memprediksi keberadaan penyakit jantung berdasarkan berbagai faktor dan gejala yang terkait. Dalam konteks ini, model Decision Tree dan Naive Bayes telah dipilih untuk dilakukan perbandingan dalam hal kemampuan prediksi.

Decision Tree adalah model klasifikasi yang menggunakan struktur pohon keputusan untuk memisahkan data berdasarkan aturan-aturan yang dihasilkan. Sementara itu, Naive Bayes adalah model klasifikasi yang berdasarkan pada Teorema Bayes dengan asumsi sederhana yaitu independensi antara fitur-fitur yang digunakan.

1.2 Tujuan :

Tujuan dari laporan ini adalah untuk menganalisis dan membandingkan performa model Decision Tree dan Naive Bayes dalam memprediksi keberadaan penyakit jantung pada pasien berdasarkan dataset Heart.

  • Target : HeartDisease.
  • Prediktor : Semua variabel, kecuali HeartDisease.
knitr::include_graphics("assets/heart.jpg")

2 EDA & Data Wrangling

2.1 Deskripsi Kolom

data <- read.csv("data_input/heart.csv")
glimpse(data)
#> Rows: 918
#> Columns: 12
#> $ Age            <int> 40, 49, 37, 48, 54, 39, 45, 54, 37, 48, 37, 58, 39, 49,…
#> $ Sex            <chr> "M", "F", "M", "F", "M", "M", "F", "M", "M", "F", "F", …
#> $ ChestPainType  <chr> "ATA", "NAP", "ATA", "ASY", "NAP", "NAP", "ATA", "ATA",…
#> $ RestingBP      <int> 140, 160, 130, 138, 150, 120, 130, 110, 140, 120, 130, …
#> $ Cholesterol    <int> 289, 180, 283, 214, 195, 339, 237, 208, 207, 284, 211, …
#> $ FastingBS      <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
#> $ RestingECG     <chr> "Normal", "Normal", "ST", "Normal", "Normal", "Normal",…
#> $ MaxHR          <int> 172, 156, 98, 108, 122, 170, 170, 142, 130, 120, 142, 9…
#> $ ExerciseAngina <chr> "N", "N", "N", "Y", "N", "N", "N", "N", "Y", "N", "N", …
#> $ Oldpeak        <dbl> 0.0, 1.0, 0.0, 1.5, 0.0, 0.0, 0.0, 0.0, 1.5, 0.0, 0.0, …
#> $ ST_Slope       <chr> "Up", "Flat", "Up", "Flat", "Up", "Up", "Up", "Up", "Fl…
#> $ HeartDisease   <int> 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1…

Penjelasan tiap kolom (metadata) :

  • Age: Usia pasien dalam tahun.
  • Sex: Jenis kelamin pasien. Nilai “M” mengindikasikan pria (male), dan nilai “F” mengindikasikan wanita (female).
  • ChestPainType: Jenis nyeri dada yang dirasakan oleh pasien. Nilai-nilai yang mungkin adalah “ATA” (Atypical Angina), “NAP” (Non-Anginal Pain), “ASY” (Asymptomatic), dan “TA” (Typical Angina).
  • RestingBP: Tekanan darah istirahat pasien dalam mm Hg (milimeter raksa).
  • Cholesterol: Kadar kolesterol serum pasien dalam mg/dl (miligram per desiliter).
  • FastingBS: Kadar gula darah puasa pasien. Nilai 0 menunjukkan kadar gula darah normal, sedangkan nilai 1 menunjukkan kadar gula darah tinggi.
  • RestingECG: Hasil elektrokardiogram (ECG) istirahat pasien. Nilai-nilai yang mungkin adalah “Normal” (normal), “ST” (ada perubahan segmen ST-T), dan “Hyp” (hipertrofi ventrikel kiri).
  • MaxHR: Denyut jantung maksimum yang dicapai oleh pasien selama tes olahraga.
  • ExerciseAngina: Kehadiran angina (nyeri dada) selama tes olahraga. Nilai “N” menunjukkan tidak hadir angina, sedangkan nilai “Y” menunjukkan hadirnya angina.
  • Oldpeak: Depresi segmen ST yang disebabkan oleh olahraga relatif terhadap istirahat.
  • ST_Slope: Kemiringan segmen ST selama puncak latihan. Nilai-nilai yang mungkin adalah “Up” (naik), “Flat” (datar), dan “Down” (turun).
  • HeartDisease: Kehadiran atau tidaknya penyakit jantung. Nilai 0 menunjukkan tidak adanya penyakit jantung, sedangkan nilai 1 menunjukkan adanya penyakit jantung.
table(data$HeartDisease)
#> 
#>   0   1 
#> 410 508

Saya mempertahankan ketidakseimbangan data, karena tujuan kali ini ialah menganalisis perbedaan karakteristik atau tren antara kategori, case ini juga sesuai dengan kondisi dunia nyata dimana kategori minoritas memang lebih jarang terjadi.

2.2 Cek Missing Value

colSums(is.na(data))
#>            Age            Sex  ChestPainType      RestingBP    Cholesterol 
#>              0              0              0              0              0 
#>      FastingBS     RestingECG          MaxHR ExerciseAngina        Oldpeak 
#>              0              0              0              0              0 
#>       ST_Slope   HeartDisease 
#>              0              0

2.3 Pemilihan variabel prediktor

dataset_selected <- data[ ,!names(data) %in% "HeartDisease" ]
dataset_selected

2.4 Ubah tipe data

data <-data %>% 
  mutate(Sex = as.factor(data$Sex),
         ChestPainType = as.factor(data$ChestPainType),
         RestingECG = as.factor(data$RestingECG),
         ST_Slope = as.factor(data$ST_Slope),
         ExerciseAngina = as.factor(data$ExerciseAngina),
         FastingBS = as.factor(data$FastingBS),
         HeartDisease = as.factor(data$HeartDisease),)

glimpse(data)
#> Rows: 918
#> Columns: 12
#> $ Age            <int> 40, 49, 37, 48, 54, 39, 45, 54, 37, 48, 37, 58, 39, 49,…
#> $ Sex            <fct> M, F, M, F, M, M, F, M, M, F, F, M, M, M, F, F, M, F, M…
#> $ ChestPainType  <fct> ATA, NAP, ATA, ASY, NAP, NAP, ATA, ATA, ASY, ATA, NAP, …
#> $ RestingBP      <int> 140, 160, 130, 138, 150, 120, 130, 110, 140, 120, 130, …
#> $ Cholesterol    <int> 289, 180, 283, 214, 195, 339, 237, 208, 207, 284, 211, …
#> $ FastingBS      <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
#> $ RestingECG     <fct> Normal, Normal, ST, Normal, Normal, Normal, Normal, Nor…
#> $ MaxHR          <int> 172, 156, 98, 108, 122, 170, 170, 142, 130, 120, 142, 9…
#> $ ExerciseAngina <fct> N, N, N, Y, N, N, N, N, Y, N, N, Y, N, Y, N, N, N, N, N…
#> $ Oldpeak        <dbl> 0.0, 1.0, 0.0, 1.5, 0.0, 0.0, 0.0, 0.0, 1.5, 0.0, 0.0, …
#> $ ST_Slope       <fct> Up, Flat, Up, Flat, Up, Up, Up, Up, Flat, Up, Up, Flat,…
#> $ HeartDisease   <fct> 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1…
summary(data)
#>       Age        Sex     ChestPainType   RestingBP      Cholesterol   
#>  Min.   :28.00   F:193   ASY:496       Min.   :  0.0   Min.   :  0.0  
#>  1st Qu.:47.00   M:725   ATA:173       1st Qu.:120.0   1st Qu.:173.2  
#>  Median :54.00           NAP:203       Median :130.0   Median :223.0  
#>  Mean   :53.51           TA : 46       Mean   :132.4   Mean   :198.8  
#>  3rd Qu.:60.00                         3rd Qu.:140.0   3rd Qu.:267.0  
#>  Max.   :77.00                         Max.   :200.0   Max.   :603.0  
#>  FastingBS  RestingECG      MaxHR       ExerciseAngina    Oldpeak       
#>  0:704     LVH   :188   Min.   : 60.0   N:547          Min.   :-2.6000  
#>  1:214     Normal:552   1st Qu.:120.0   Y:371          1st Qu.: 0.0000  
#>            ST    :178   Median :138.0                  Median : 0.6000  
#>                         Mean   :136.8                  Mean   : 0.8874  
#>                         3rd Qu.:156.0                  3rd Qu.: 1.5000  
#>                         Max.   :202.0                  Max.   : 6.2000  
#>  ST_Slope   HeartDisease
#>  Down: 63   0:410       
#>  Flat:460   1:508       
#>  Up  :395               
#>                         
#>                         
#> 

Diperoleh Insight :

  • Umur (Age): Rata-rata usia penumpang adalah 53.51 tahun, dengan rentang usia antara 28 hingga 77 tahun.
  • Jenis Kelamin (Sex): Terdapat 193 penumpang perempuan dan 725 penumpang laki-laki dalam dataset.
  • Jenis Nyeri Dada (ChestPainType): Terdapat 4 jenis nyeri dada, yaitu ASY, ATA, NAP, dan TA. Jenis yang paling umum adalah ASY dengan 496 observasi.
  • Tekanan Darah Istirahat (RestingBP): Tekanan darah istirahat rata-rata adalah 132.4, dengan rentang antara 0 hingga 200.
  • Kolesterol (Cholesterol): Kolesterol rata-rata adalah 198.8, dengan rentang antara 0 hingga 603.
  • Gula Darah Puasa (FastingBS): Sekitar 23.31% penumpang memiliki gula darah puasa yang lebih tinggi dari normal.
  • Denyut Jantung Maksimal (MaxHR): Rata-rata denyut jantung maksimal adalah 136.8, dengan rentang antara 60 hingga 202.
  • Penyakit Jantung (HeartDisease): Sekitar 55.34% penumpang dalam dataset memiliki penyakit jantung (nilai 1), sementara sisanya tidak memiliki penyakit jantung (nilai 0)

2.5 Pisahkan variabel target dari dataset

target <- data$HeartDisease

2.6 Visual Basic

# Histogram usia (Age)
ggplot(data, aes(x = Age)) +
  geom_histogram(fill = "steelblue", color = "black") +
  labs(x = "Usia", y = "Frekuensi") +
  ggtitle("Distribusi Usia") +
  theme_minimal()

# Box plot kadar kolesterol (Cholesterol) berdasarkan jenis nyeri dada (ChestPainType)
ggplot(data, aes(x = ChestPainType, y = Cholesterol, fill = ChestPainType)) +
  geom_boxplot() +
  labs(x = "Jenis Nyeri Dada", y = "Kadar Kolesterol") +
  ggtitle("Perbandingan Kadar Kolesterol\nBerdasarkan Jenis Nyeri Dada") +
  theme_minimal()

Note ::ATA” (Atypical Angina) “NAP” (Non-Anginal Pain) “ASY” (Asymptomatic) “TA” (Typical Angina)

# keberadaan penyakit jantung (HeartDisease) berdasarkan jenis nyeri dada (ChestPainType) dan jenis kelamin (Sex)
ggplot(data, aes(x = ChestPainType, fill = factor(HeartDisease))) +
  geom_bar(position = "fill") +
  facet_wrap(~Sex) +
  labs(x = "Jenis Nyeri Dada", y = "Persentase") +
  ggtitle("Persentase Penyakit Jantung\nBerdasarkan Jenis Nyeri Dada dan Jenis Kelamin") +
  theme_minimal()+
   coord_flip()

# Bar plot jenis kelamin (Sex)
ggplot(data, aes(x = Sex)) +
  geom_bar(fill = "steelblue") +
  labs(x = "Jenis Kelamin", y = "Jumlah") +
  ggtitle("Distribusi Peserta Berdasarkan Jenis Kelamin") +
  theme_minimal()

3 Modelling

3.1 Cross Validation

RNGkind(sample.kind = "Rounding")
set.seed(123)

# index sampling
index <- sample(x = nrow(data),
                size = nrow(data)*0.8) 

# splitting
data_train <- data[index, ]
data_test <- data[-index, ] 

3.2 Fitting Models

3.2.0.1 Naive Bayes

library(caret)
model_nb <- train(HeartDisease ~ ., data = data_train, method ="naive_bayes")
model_nb
#> Naive Bayes 
#> 
#> 734 samples
#>  11 predictor
#>   2 classes: '0', '1' 
#> 
#> No pre-processing
#> Resampling: Bootstrapped (25 reps) 
#> Summary of sample sizes: 734, 734, 734, 734, 734, 734, ... 
#> Resampling results across tuning parameters:
#> 
#>   usekernel  Accuracy   Kappa    
#>   FALSE      0.8605491  0.7178287
#>    TRUE      0.8526397  0.6989693
#> 
#> Tuning parameter 'laplace' was held constant at a value of 0
#> Tuning
#>  parameter 'adjust' was held constant at a value of 1
#> Accuracy was used to select the optimal model using the largest value.
#> The final values used for the model were laplace = 0, usekernel = FALSE
#>  and adjust = 1.

3.2.0.2 Prediksi

nb_pred_train <- predict(model_nb, newdata = data_train)
nb_pred_test <- predict(model_nb, newdata = data_test)

3.2.0.3 Evaluasi

nb_train_accuracy <- mean(nb_pred_train == data_train$HeartDisease)
nb_test_accuracy <- mean(nb_pred_test == data_test$HeartDisease)

3.2.0.4 Confusion Matriks

# Naive Bayes
confusionMatrix(nb_pred_test, data_test$HeartDisease)
#> Confusion Matrix and Statistics
#> 
#>           Reference
#> Prediction  0  1
#>          0 67  9
#>          1 12 96
#>                                           
#>                Accuracy : 0.8859          
#>                  95% CI : (0.8308, 0.9279)
#>     No Information Rate : 0.5707          
#>     P-Value [Acc > NIR] : <2e-16          
#>                                           
#>                   Kappa : 0.766           
#>                                           
#>  Mcnemar's Test P-Value : 0.6625          
#>                                           
#>             Sensitivity : 0.8481          
#>             Specificity : 0.9143          
#>          Pos Pred Value : 0.8816          
#>          Neg Pred Value : 0.8889          
#>              Prevalence : 0.4293          
#>          Detection Rate : 0.3641          
#>    Detection Prevalence : 0.4130          
#>       Balanced Accuracy : 0.8812          
#>                                           
#>        'Positive' Class : 0               
#> 

3.2.0.5 Decission Tree

tree_model <- train(HeartDisease ~ ., data = data_train, method = "rpart")
tree_model
#> CART 
#> 
#> 734 samples
#>  11 predictor
#>   2 classes: '0', '1' 
#> 
#> No pre-processing
#> Resampling: Bootstrapped (25 reps) 
#> Summary of sample sizes: 734, 734, 734, 734, 734, 734, ... 
#> Resampling results across tuning parameters:
#> 
#>   cp          Accuracy   Kappa    
#>   0.01812689  0.8284746  0.6519166
#>   0.05135952  0.8176127  0.6282037
#>   0.58610272  0.6755823  0.3067103
#> 
#> Accuracy was used to select the optimal model using the largest value.
#> The final value used for the model was cp = 0.01812689.

3.2.0.6 Prediksi

tree_pred_train <- predict(tree_model, newdata = data_train)
tree_pred_test <- predict(tree_model, newdata = data_test)

3.2.0.7 Evaluasi

tree_train_accuracy <- mean(tree_pred_train == data_train$HeartDisease)
tree_test_accuracy <- mean(tree_pred_test == data_test$HeartDisease)

3.2.0.8 Confusion Matriks

# Decision Tree
confusionMatrix(tree_pred_test, data_test$HeartDisease)
#> Confusion Matrix and Statistics
#> 
#>           Reference
#> Prediction  0  1
#>          0 59 11
#>          1 20 94
#>                                           
#>                Accuracy : 0.8315          
#>                  95% CI : (0.7695, 0.8826)
#>     No Information Rate : 0.5707          
#>     P-Value [Acc > NIR] : 4.032e-14       
#>                                           
#>                   Kappa : 0.6513          
#>                                           
#>  Mcnemar's Test P-Value : 0.1508          
#>                                           
#>             Sensitivity : 0.7468          
#>             Specificity : 0.8952          
#>          Pos Pred Value : 0.8429          
#>          Neg Pred Value : 0.8246          
#>              Prevalence : 0.4293          
#>          Detection Rate : 0.3207          
#>    Detection Prevalence : 0.3804          
#>       Balanced Accuracy : 0.8210          
#>                                           
#>        'Positive' Class : 0               
#> 

3.3 Perbandingan Model

comparison <- data.frame(
  Model = c("Decision Tree", "Naive Bayes"),
  Train_Accuracy = c(tree_train_accuracy, nb_train_accuracy),
  Test_Accuracy = c(tree_test_accuracy, nb_test_accuracy)
)

comparison

Note :

Saya menggunakan metrik akurasi, karena akurasi merupakan metrik yang paling umum digunakan dalam klasifikasi, dan itu mengukur persentase prediksi yang benar dibandingkan dengan jumlah total prediksi.

3.4 Metrik lain

# Calculate precision
tree_train_precision <- precision(tree_pred_train, data_train$HeartDisease)
tree_test_precision <- precision(tree_pred_test, data_test$HeartDisease)
nb_train_precision <- precision(nb_pred_train, data_train$HeartDisease)
nb_test_precision <- precision(nb_pred_test, data_test$HeartDisease)

# Calculate recall
tree_train_recall <- recall(tree_pred_train, data_train$HeartDisease)
tree_test_recall <- recall(tree_pred_test, data_test$HeartDisease)
nb_train_recall <- recall(nb_pred_train, data_train$HeartDisease)
nb_test_recall <- recall(nb_pred_test, data_test$HeartDisease)

# Create dataframe
comparison_df <- data.frame(
  Model = c("Decision Tree", "Naive Bayes"),
  Train_Precision = c(tree_train_precision, nb_train_precision),
  Test_Precision = c(tree_test_precision, nb_test_precision),
  Train_Recall = c(tree_train_recall, nb_train_recall),
  Test_Recall = c(tree_test_recall, nb_test_recall)
)

comparison_df

Insight :

Dari hasil comparison diatas, diperoleh insight :

  • Dalam hal akurasi, kedua model memiliki kinerja yang baik pada data pelatihan maupun pengujian. Decision Tree memiliki akurasi sedikit lebih rendah daripada Naive Bayes pada data pengujian.
  • Dalam hal precision, Decision Tree memiliki nilai presisi yang lebih tinggi pada data pelatihan, tetapi Naive Bayes memiliki nilai presisi yang lebih tinggi pada data pengujian. Hal ini menunjukkan bahwa Naive Bayes cenderung lebih baik dalam mengidentifikasi kasus positif dengan benar pada data yang belum pernah dilihat sebelumnya.
  • Dalam hal recall, Naive Bayes memiliki nilai recall yang lebih tinggi baik pada data pelatihan maupun pengujian. Hal ini menunjukkan bahwa Naive Bayes cenderung lebih baik dalam menemukan kasus positif secara keseluruhan.

4 Penutup

Berdasarkan hasil perbandingan model, dapat disimpulkan bahwa kedua model memiliki performa yang baik. Namun, Naive Bayes memiliki nilai presisi dan recall yang lebih tinggi pada data pengujian, sehingga bisa menjadi pilihan yang lebih baik untuk kasus ini. Model Naive Bayes cenderung lebih baik dalam mengidentifikasi kasus positif dengan benar pada data yang belum pernah dilihat sebelumnya.

4.1 Saran :

Berdasarkan hasil analisis, disarankan untuk menggunakan model Naive Bayes dalam memprediksi keberadaan penyakit jantung pada pasien. Namun, penting juga untuk terus melakukan evaluasi dan pengembangan model dengan menggunakan lebih banyak data dan menerapkan teknik tuning model yang lebih canggih untuk meningkatkan performa prediksi.