## age gender chest_pain_type blood_pressure cholesterol max_heart_rate
## 1 24 1 4 250 139 212
## 2 29 0 4 132 187 147
## 3 46 0 3 271 185 193
## 4 73 NA 2 102 200 125
## 5 49 1 3 91 163 192
## 6 63 1 3 18 154 107
## 7 48 0 3 143 275 165
## 8 37 1 4 263 201 201
## 9 20 0 3 113 127 139
## 10 77 1 1 138 217 201
## exercise_angina plasma_glucose skin_thickness insulin bmi
## 1 0 108 33 109 37.99930
## 2 0 202 42 NA 25.58835
## 3 0 149 43 102 37.89203
## 4 0 105 77 165 18.66024
## 5 0 162 31 170 12.76798
## 6 0 103 67 102 22.37385
## 7 0 248 NA 136 27.90071
## 8 0 186 21 180 35.66340
## 9 1 123 NA 120 26.52915
## 10 0 199 100 132 18.39360
## diabetes_pedigree hypertension heart_disease residence_type smoking_status
## 1 0.4802775 1 1 Urban Smoker
## 2 0.2839864 1 1 Urban Unknown
## 3 2.4723086 1 0 Rural Non-Smoker
## 4 1.4720523 0 1 Rural Smoker
## 5 0.5376265 1 1 Rural Smoker
## 6 1.0624109 0 0 Rural Non-Smoker
## 7 1.0737608 1 1 Rural Non-Smoker
## 8 0.1512359 0 0 Urban Smoker
## 9 1.9102780 1 0 Urban Non-Smoker
## 10 1.8253058 1 0 Rural Non-Smoker
data %>%
summarise(across(everything(), ~sum(is.na(.)))) %>%
pivot_longer(everything(), names_to = "Kolom", values_to = "Jumlah_NA") %>%
mutate(Persen_NA = round((Jumlah_NA / nrow(data)) * 100, 2))## # A tibble: 16 × 3
## Kolom Jumlah_NA Persen_NA
## <chr> <int> <dbl>
## 1 age 0 0
## 2 gender 472 7.87
## 3 chest_pain_type 0 0
## 4 blood_pressure 0 0
## 5 cholesterol 0 0
## 6 max_heart_rate 0 0
## 7 exercise_angina 0 0
## 8 plasma_glucose 609 10.2
## 9 skin_thickness 614 10.2
## 10 insulin 568 9.47
## 11 bmi 0 0
## 12 diabetes_pedigree 0 0
## 13 hypertension 0 0
## 14 heart_disease 0 0
## 15 residence_type 0 0
## 16 smoking_status 0 0
# Cek nilai kosong " " atau spasi pada kolom karakter
sapply(data, function(x) {
if (is.character(x) || is.factor(x)) sum(trimws(x) == "") else 0
})## age gender chest_pain_type blood_pressure
## 0 0 0 0
## cholesterol max_heart_rate exercise_angina plasma_glucose
## 0 0 0 0
## skin_thickness insulin bmi diabetes_pedigree
## 0 0 0 0
## hypertension heart_disease residence_type smoking_status
## 0 0 455 0
Dari hasil cek nilai kosong pada setiap kolom dataset, ditemukan hampir seluruh kolom tidak memiliki nilai kosong, kecuali kolom residence_type terdapat 455 baris data memiliki nilai kosong, yang berarti baris-baris tersebut tidak memiliki informasi tentang jenis tempat tinggal pasien.
ohe_encode <- function(df, column) {
dummies <- dummyVars(as.formula(paste("~", column)), data = df)
ohe <- predict(dummies, newdata = df)
colnames(ohe) <- gsub("\\.", "_", colnames(ohe))
ohe_df <- as.data.frame(ohe)
df <- cbind(df, ohe_df)
df <- df[, !(names(df) %in% column)]
return(df)
}
data <- ohe_encode(data, "residence_type")
data <- ohe_encode(data, "gender")
data <- ohe_encode(data, "smoking_status")data %>%
summarise(across(everything(), ~sum(is.na(.)))) %>%
pivot_longer(everything(), names_to = "Kolom", values_to = "Jumlah_NA") %>%
mutate(Persen_NA = round((Jumlah_NA / nrow(data)) * 100, 2))## # A tibble: 18 × 3
## Kolom Jumlah_NA Persen_NA
## <chr> <int> <dbl>
## 1 age 0 0
## 2 chest_pain_type 0 0
## 3 blood_pressure 0 0
## 4 cholesterol 0 0
## 5 max_heart_rate 0 0
## 6 exercise_angina 0 0
## 7 plasma_glucose 0 0
## 8 skin_thickness 0 0
## 9 insulin 0 0
## 10 bmi 0 0
## 11 diabetes_pedigree 0 0
## 12 hypertension 0 0
## 13 heart_disease 0 0
## 14 residence_typeRural 0 0
## 15 residence_typeUrban 0 0
## 16 smoking_statusNon-Smoker 0 0
## 17 smoking_statusSmoker 0 0
## 18 smoking_statusUnknown 0 0
db_result <- dbscan(data_scaled, eps = 3, minPts = 4)
fviz_cluster(list(data = data_scaled, cluster = db_result$cluster),
main = "DBSCAN Clustering")
Parameter ε (epsilon) pada DBSCAN ditentukan menggunakan kNN distance
plot dengan k = 4 sehingga minPts = 4. Untuk grafiknya menunjukkan elbow
atau tekukan di nilai 3, sehingga ditetapkan eps = 3. Garis merah
putus-putus pada grafik menandai ambang epsilon yang digunakan dalam
algoritma.
Setelah parameter ditentukan, dilanjutkan klasterisasi menggunakan DBSCAN. Hasil visualisasi menunjukkan bahwa algoritma berhasil mengidentifikasi beberapa klaster yang terpisah, namun masih cukup banyak data yang diklasifikasikan sebagai noise yang ditandai dengan label 0 dan simbol merah. Hal ini mengindikasikan bahwa data asli masih cukup kompleks dan tumpang tindih antar grup pasien.
silhouette_dbscan <- silhouette(db_result$cluster, dist(data_scaled))
silhouette_score_dbscan <- mean(silhouette_dbscan[, 3]) # Nilai rata-rata Silhouette
# Hitung jumlah noise points dengan label 0
noise_points_dbscan <- sum(db_result$cluster == 0)
cat("Silhouette Score DBSCAN tanpa PCA:", silhouette_score_dbscan, "\n")## Silhouette Score DBSCAN tanpa PCA: 0.07383255
## Number of Noise Points DBSCAN tanpa PCA: 342
Pada bagian ini didapatkan Silhouette Score DBSCAN sebesar 0.074, yang menandakan kualitas klaster masih tergolong rendah. Hal ini terjadi karena dimensi data yang tinggi, serta tumpang tindih antar kelompok
Sebanyak 342 data atau sekitar 5.7% dari total 6000 observasi diklasifikasikan sebagai noise karena tidak masuk ke dalam klaster manapun.
## PC1 PC2 PC3 PC4 PC5 PC6 PC7 PC8 PC9 PC10
## 0.10366 0.20654 0.27089 0.33078 0.38998 0.44797 0.50503 0.56139 0.61739 0.67246
## PC11 PC12 PC13 PC14 PC15 PC16 PC17 PC18
## 0.72705 0.78132 0.83493 0.88817 0.94046 0.99219 1.00000 1.00000
# Hitung variansi kumulatif
cum_var <- summary(pca_res)$importance[3, ]
n_components <- which(cum_var >= 0.8)[1]
pca_data <- as.data.frame(pca_res$x[, 1:n_components])Hasil visualisasi scree plot menunjukkan bahwa komponen utama pertama PC1 dan PC2 menyumbang variasi yang besar dibandingkan komponen lainnya. Berdasarkan output variansi kumulatif, diketahui bahwa:
10 komponen utama pertama telah menjelaskan sekitar 67.25% dari total variansi data.
Sementara lanjutannya hingga PC13, data telah menjelaskan 83.49% variansi kumulatif.
Untuk memenuhi ambang minimal 80% variansi, dipilih 13 komponen utama pertama sebagai representasi data yang direduksi, yang ditandai pada kode n_components = 13. Dengan demikian, PCA dapat menyederhanakan data dari 18 fitur menjadi 13 komponen dan tetap mempertahankan informasi penting, sehingga dapat digunakan untuk klasterisasi yang lebih optimal dan cepat secara komputasi.
db_result <- dbscan(pca_data, eps = 3, minPts = 4)
fviz_cluster(list(data = pca_data, cluster = db_result$cluster),
main = "DBSCAN Clustering")
Setelah data direduksi dimensinya menggunakan PCA menjadi 13 komponen
utama, selanjutnya dilakukan klasterisasi menggunakan DBSCAN. Untuk
menentukan nilai epsilon yang optimal, penggunaan kNN distance plot
dengan k = 4, titik elbow 3 pada grafik dipertahankan untuk
konsistensi.
Hasil visualisasi klaster menunjukkan adanya dua klaster utama yang terbentuk dengan jelas, yaitu:
Cluster 1 warna hijau
Cluster 2 warna biru
Selain itu, ada sedikit data yang diklasifikasikan sebagai noise yang berwarna merah atau cluster 0, yang jumlahnya lebih sedikit dibandingkan saat DBSCAN dijalankan tanpa PCA
silhouette_dbscan_pca <- silhouette(db_result$cluster, dist(pca_data))
silhouette_score_dbscan <- mean(silhouette_dbscan_pca[, 3])
noise_points_dbscan <- sum(db_result$cluster == 0)
cat("Silhouette Score DBSCAN dengan PCA:", silhouette_score_dbscan, "\n")## Silhouette Score DBSCAN dengan PCA: 0.1826298
## Number of Noise Points DBSCAN dengan PCA: 7
Hasil klasterisasi menunjukkan adanya peningkatan yang signifikan dibandingkan hasil klaster tanpa PCA. Jumlah data yang dikategorikan sebagai noise hanya sebanyak 7 data, jauh lebih sedikit dibandingkan dengan hasil tanpa PCA.
Selain itu, nilai Silhouette Score meningkat menjadi 0,1826, yang menunjukkan bahwa pembentukan klaster menjadi lebih baik, dengan pemisahan antar klaster yang lebih jelas serta kohesi yang lebih kuat di dalam klaster
silhouette_df <- data.frame(
Metode = c("Tanpa PCA", "Dengan PCA"),
Silhouette = c(silhouette_score_dbscan, noise_points_dbscan)
)
ggplot(silhouette_df, aes(x = Metode, y = Silhouette, fill = Metode)) +
geom_bar(stat = "identity", width = 0.4) +
ggtitle("Perbandingan Silhouette Score") +
theme_minimal()## cluster size ave.sil.width
## 0 0 342 -0.18
## 1 1 5333 0.09
## 2 2 146 0.03
## 3 3 169 -0.01
## 4 4 6 0.31
## 5 5 4 0.44
Hasil silhouette plot tanpa PCA menunjukkan bahwa sebagian besar klaster memiliki nilai average silhouette width yang rendah, bahkan negatif pada klaster 0 dan 3, yang menunjukkan adanya tumpang tindih antar klaster dan pemisahan yang kurang optimal. Secara keseluruhan, kualitas klasterisasi tanpa PCA belum memuaskan.
## cluster size ave.sil.width
## 0 0 7 -0.20
## 1 1 5522 0.18
## 2 2 471 0.21
Hasil silhouette plot dengan PCA menunjukkan nilai average silhouette width meningkat, khususnya pada klaster 1 dan 2, yang masing-masing memiliki nilai 0,18 dan 0,21. Ini menunjukkan bahwa setelah reduksi dimensi, pemisahan antar klaster menjadi lebih jelas dan kohesi antar anggota klaster meningkat dengan hanya 7 data yang tergolong noise dengan nilai silhouette negatif