1 Load Dataset

data <- read.csv("patient_dataset.csv")
head(data, n = 10)
##    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

2 Preprocessing

2.1 Cek Missing Value dan Nilai Kosong

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 kolom gender, plasma_glucose, skin_thickness, dan insulin memiliki nilai kosong (NA). Selain itu, terdapat 455 baris nilai dengan kolom kosong (” “) pada kolom residence_type, yang berarti baris-baris tersebut tidak memiliki informasi tentang jenis tempat tinggal pasien.

2.2 Imputasi Data Menggunakan KNN

list_impute <- c("plasma_glucose", "skin_thickness", "insulin")
data[list_impute] <- kNN(data[list_impute], k = 5)[, list_impute]

2.3 Ubah Kolom Kategorikal ke Numerik

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")

2.4 Cek Ulang Missing Value

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

2.5 Scaling

scaler <- preProcess(data, method = c("center", "scale"))
data_scaled <- predict(scaler, data)

3 K-Means tanpa PCA

3.1 Menentukan Jumlah Cluster

# Elbow Method
fviz_nbclust(data_scaled, kmeans, method = "wss") +
  labs(title = "Elbow Method", x = "Jumlah Cluster", y = "WSS")

# Silhouette Method
fviz_nbclust(data_scaled, kmeans, method = "silhouette") +
  labs(title = "Silhouette Method")

3.2 K-Means

set.seed(123)
kmeans_result <- kmeans(data_scaled, centers = 3, nstart = 25)
fviz_cluster(list(data = data_scaled, cluster = kmeans_result$cluster),
             main = "K-Means Clustering")

Hasil clustering K-Means tanpa PCA menunjukkan bahwa pemilihan tiga klaster didukung oleh metode Silhouette dan Elbow yang konsisten, dengan nilai silhouette tertinggi sekitar 0,12. Visualisasi scatter plot memperlihatkan ketiga klaster terpisah dengan jelas.

3.3 Evaluasi K-Means

silhouette_kmeans <- silhouette(kmeans_result$cluster, dist(data_scaled))
silhouette_score_kmeans <- mean(silhouette_kmeans[, 3])  # Nilai rata-rata Silhouette

wss_kmeans <- kmeans_result$tot.withinss  # WSS (Within-Cluster Sum of Squares)

cat("Silhouette Score K-Meanstanpa PCA:", silhouette_score_kmeans, "\n")
## Silhouette Score K-Meanstanpa PCA: 0.1246539
cat("WSS (Within-Cluster Sum of Squares) K-Means tanpa PCA:", wss_kmeans, "\n")
## WSS (Within-Cluster Sum of Squares) K-Means tanpa PCA: 89968.95

Nilai Silhouette Score K-Means sebesar 0,12 menunjukkan bahwa pemisahan klaster pada dataset ini masih tergolong rendah hingga sedang. Namun, skor ini masih cukup untuk menunjukkan adanya pola pengelompokan. Nilai WSS sebesar 89.968,95 mengindikasikan tingkat kepadatan klaster, di mana semakin kecil nilai WSS semakin rapat data dalam klaster terhadap centroidnya.

4 K-Means dengan PCA

4.1 PCA

pca_res <- prcomp(data_scaled, center = TRUE, scale. = TRUE)
fviz_eig(pca_res)

summary(pca_res)$importance[3, ]  # variansi kumulatif
##     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:3])

Hasil visualisasi scree plot menunjukkan bahwa komponen utama pertama (PC1) hingga komponen kedua (PC2) menyumbang proporsi variansi yang cukup besar dibandingkan komponen-komponen berikutnya. Grafik memperlihatkan pola “elbow” yang menandakan penurunan signifikan dalam kontribusi variansi setelah beberapa komponen awal.

Namun demikian, dalam konteks klasterisasi, tujuan utama bukan hanya mempertahankan sebanyak mungkin variansi data, melainkan merepresentasikan struktur spasial data secara optimal agar klaster yang terbentuk lebih kompak dan terpisah dengan jelas. Perlu dipahami bahwa variansi yang tinggi belum tentu relevan dalam proses klasterisasi apabila informasi yang dikandung tidak berkontribusi terhadap pemisahan jarak antar titik data.

Oleh karena itu, pemilihan jumlah komponen utama tidak semata-mata didasarkan pada nilai variansi kumulatif, melainkan juga mempertimbangkan kualitas hasil klasterisasi—misalnya melalui nilai Silhouette Score. Berdasarkan pertimbangan tersebut, tiga komponen utama pertama dipilih untuk digunakan dalam proses klasterisasi.

4.2 K-Means

set.seed(123)
kmeans_pca <- kmeans(pca_data, centers = 3, nstart = 25)

fviz_cluster(list(data = pca_data, cluster = kmeans_pca$cluster),
             main = "K-Means Clustering dengan PCA")

4.3 Evaluasi K-Means

silhouette_kmeans_pca <- silhouette(kmeans_pca$cluster, dist(pca_data))
silhouette_score_kmeans_pca <- mean(silhouette_kmeans_pca[, 3])
wss_kmeans_pca <- kmeans_pca$tot.withinss

cat("Silhouette Score (Dengan PCA):", silhouette_score_kmeans_pca, "\n")
## Silhouette Score (Dengan PCA): 0.5484953
cat("WSS (Dengan PCA):", wss_kmeans_pca, "\n")
## WSS (Dengan PCA): 11596.8

Setelah data direduksi dimensinya menggunakan PCA menjadi 3 komponen utama, selanjutnya dilakukan klasterisasi menggunakan K-Means.

Hasil visualisasi klaster menunjukkan adanya tiga klaster utama yang terbentuk, yaitu:

  • Cluster 1 warna merah

  • Cluster 2 warna hijau

  • Cluster 3 warna biru

Dengan silhouette score 0.548 dan WSS 11596.8, pengelompokan klaster lebih terlihat terpisah meski antar klaster masih nampak tumpang tindih.

5 Visualisasi Pebandingan

5.1 Barplot WSS

wss_df <- data.frame(
  Metode = c("Tanpa PCA", "Dengan PCA"),
  WSS = c(wss_kmeans, wss_kmeans_pca)
)

ggplot(wss_df, aes(x = Metode, y = WSS, fill = Metode)) +
  geom_bar(stat = "identity", width = 0.4) +
  ggtitle("Perbandingan WSS") +
  theme_minimal()

5.2 Barplot Silhoutte Score

silhouette_df <- data.frame(
  Metode = c("Tanpa PCA", "Dengan PCA"),
  Silhouette = c(silhouette_score_kmeans, silhouette_score_kmeans_pca)
)

ggplot(silhouette_df, aes(x = Metode, y = Silhouette, fill = Metode)) +
  geom_bar(stat = "identity", width = 0.4) +
  ggtitle("Perbandingan Silhouette Score") +
  theme_minimal()

5.3 Silhouette Plot – K-Means tanpa PCA

fviz_silhouette(silhouette_kmeans) +
  ggtitle("Silhouette Plot - K-Means (Tanpa PCA)")
##   cluster size ave.sil.width
## 1       1  476          0.22
## 2       2 2786          0.11
## 3       3 2738          0.12

Hasil clustering menggunakan K-Means tanpa PCA menghasilkan tiga klaster dengan ukuran yang kurang seimbang. Klaster 1 terdiri dari 476 data dengan rata-rata nilai silhouette tertinggi sebesar 0,22, sedangkan klaster 2 dan klaster 3 masing-masing beranggotakan 2.786 dan 2.738 data, dengan rata-rata silhouette yang lebih rendah, yaitu 0,11 dan 0,12.

Plot silhouette menunjukkan bahwa sebagian besar data memiliki nilai positif, namun relatif rendah, yang mengindikasikan bahwa pemisahan antar klaster masih bersifat moderat. Beberapa data berada di dekat batas antar klaster, sehingga meningkatkan potensi tumpang tindih. Klaster 1 menunjukkan kualitas pemisahan terbaik di antara ketiganya, sedangkan klaster 2 dan 3 menunjukkan pemisahan yang kurang tajam.

Secara keseluruhan, segmentasi menggunakan K-Means tanpa PCA memberikan hasil yang cukup baik, namun masih terdapat ruang untuk perbaikan terutama dalam meningkatkan pemisahan antar klaster.

5.4 Silhouette Plot – K-Means dengan PCA

fviz_silhouette(silhouette_kmeans_pca) +
  ggtitle("Silhouette Plot - K-Means (Dengan PCA)")
##   cluster size ave.sil.width
## 1       1 2738          0.54
## 2       2 2786          0.54
## 3       3  476          0.65

Hasil clustering K-Means dengan PCA menunjukkan tiga klaster dengan ukuran yang relatif seimbang, yaitu klaster 1 sebanyak 2.738 data, klaster 2 sebanyak 2.786 data, dan klaster 3 berisi 476 data. Rata-rata nilai silhouette pada ketiga klaster tergolong tinggi, yaitu masing-masing sebesar 0,54 untuk klaster 1 dan 2, serta 0,65 untuk klaster 3, yang merupakan nilai tertinggi.

Plot silhouette memperlihatkan bahwa mayoritas data memiliki nilai positif dan cukup tinggi, menandakan pemisahan klaster yang baik dan kohesi internal yang kuat. Klaster 3, meskipun berukuran paling kecil, menunjukkan kualitas pemisahan terbaik. Sementara itu, klaster 1 dan 2 yang berukuran lebih besar juga menunjukkan stabilitas klaster yang baik dengan nilai silhouette yang konsisten.

Secara keseluruhan, penerapan PCA sebelum K-Means berhasil meningkatkan kualitas segmentasi, menghasilkan klaster yang lebih kompak, terpisah dengan jelas, dan berukuran seimbang dibandingkan dengan K-Means tanpa PCA.

6 Referensi

  1. AFIT Data Science Lab. (n.d.). K-means Cluster Analysis – R Programming Guide. https://afit-r.github.io/kmeans_clustering
  2. RDocumentation. (n.d.). kmeans function (stats package). https://www.rdocumentation.org/packages/stats/versions/3.6.2/topics/kmeans