1 Load Dataset

data <- read.csv("data/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 Kososng

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 Fuzzy C-Means tanpa PCA

3.1 Fuzzy C-Means

fcm_result <- cmeans(data_scaled, centers = 3, m = 2)
fviz_cluster(list(data = data_scaled, cluster = fcm_result$cluster),
             main = "Fuzzy C-Means Clustering tanpa PCA)")

Proses lanjutan dari preprocessing adalah analisis menggunakan algoritma Fuzzy C-Means (FCM) tanpa PCA. Parameter jumlah klaster ditentukan 3 cluster sesuai asumsi awal segmentasi yang diinginkan. Selain itu digunakan parameter fuzziness m = 2, yang merupakan nilai default umum untuk mengatur derajat keanggotaan ganda dimana semakin besar m, semakin kabur batas klasternya.

Visualisasi Fuzzy C-Means tanpa PCA menunjukkan tiga klaster yang terpisah dengan jelas, masing-masing ditandai oleh warna merah, hijau, dan biru. Metode Fuzzy C-Means membolehkan setiap data memiliki keanggotaan pada lebih dari satu klaster, sehingga area antar klaster saling tumpang tindih dan batas klaster kurang tegas.

3.2 Evaluasi Fuzzy C-Means

silhouette_fcm <- silhouette(fcm_result$cluster, dist(data_scaled))
silhouette_score_fcm <- mean(silhouette_fcm[, 3])

cat("Silhouette Score Fuzzy C-Means tanpa PCA:", silhouette_score_fcm)
## Silhouette Score Fuzzy C-Means tanpa PCA: 0.08152587

Pada bagian ini didapatkan Silhouette Score Fuzzy C-Means sebesar 0.098, yang menandakan kualitas klaster masih tergolong rendah. Hal ini terjadi karena dimensi data yang tinggi, serta tumpang tindih antar klaster.

4 Fuzzy C-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 Fuzzy C-Means

fcm_pca <- cmeans(pca_data, centers = 3, m = 2)
fviz_cluster(list(data = pca_data, cluster = fcm_pca$cluster),
             main = "Fuzzy C-Means Clustering dengan PCA")

4.3 Evaluasi Fuzzy C-Means

silhouette_fcm_pca <- silhouette(fcm_pca$cluster, dist(pca_data))
silhouette_score_fcm_pca <- mean(silhouette_fcm_pca[, 3])

cat("Silhouette Score Fuzzy C-Means dengan PCA:", silhouette_score_fcm_pca)
## Silhouette Score Fuzzy C-Means dengan PCA: 0.5341565

Setelah data direduksi dimensinya menggunakan PCA menjadi 3 komponen utama, selanjutnya dilakukan klasterisasi menggunakan Fuzzy C-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.534, pengelompokan klaster lebih terlihat terpisah meski antar klaster masih nampak tumpang tindih

5 Visualisasi Perbandingan

5.1 Barplot Silhoutte Score

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

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

5.2 Silhouette Plot – Fuzzy C-Means tanpa PCA

fviz_silhouette(silhouette_fcm) +
  ggtitle("Silhouette Plot - Fuzzy C-Means (Tanpa PCA)")
##   cluster size ave.sil.width
## 1       1 2981          0.08
## 2       2    4          0.34
## 3       3 3015          0.08

Hasil silhouette plot tanpa PCA menunjukkan tiga klaster dengan ukuran yang tidak seimbang, dimana klaster 1 dan klaster 2 mendominasi dengan 2825 data dan 2951 data. Sedangkan klaster 3 terendah berisi 224 data. Rata-rata nilai silhouette pada ketiga klaster relatif rendah, yaitu hanya 0.12 unruk klaster 1, 0.08 unruk klaster 2, dan 0.26 untuk klaster 3, yang mengindikasikan bahwa pemisahan antar klaster kurang optimal dan terdapat tumpang tindih yang cukup besar antar anggota klaster. Nilai-nilai ini mencerminkan bahwa struktur klaster yang terbentuk belum cukup baik dalam memisahkan kelompok data secara jelas.

5.3 Silhouette Plot – Fuzzy C-Means dengan PCA

fviz_silhouette(silhouette_fcm_pca) +
  ggtitle("Silhouette Plot - Fuzzy C-Means (Dengan PCA)")
##   cluster size ave.sil.width
## 1       1 1621          0.57
## 2       2 1560          0.60
## 3       3 2819          0.47

Hasil silhouette plot menunjukkan pembentukan tiga klaster dengan ukuran yang cukup seimbang, yakni klaster 1 sebanyak 1547 data, klaster 2 sebanyak 1683 data, dan klaster 3 sebanyak 2770 data. Rata-rata nilai silhouette untuk masing-masing klaster adalah 0,61 untuk klaster 1, 0,57 untuk klaster 2, dan 0,47 untuk klaster 3, yang mencerminkan kualitas pemisahan klaster yang cukup baik. Nilai silhouette tertinggi terdapat pada klaster 1, menunjukkan pemisahan yang jelas dari klaster lain, diikuti oleh klaster 2 dan klaster 3.

Hal ini mengindikasikan bahwa penggunaan PCA berkontribusi positif dalam membentuk struktur klaster yang lebih jelas dan proporsional, dibandingkan clustering tanpa PCA.

6 Referensi

  1. RPubs. (2023, January 21). Algoritma Fuzzy C-Means dengan R. Retrieved from https://rpubs.com/annisashrmn22/993952
  2. RPubs. (2021, June 26). Fuzzy C-Means Clustering. Retrieved from https://rpubs.com/writetosamadalvi/Fuzzy
  3. Widianti, R., Surono, S., & Ibraheem, K. I. (2004, November 2). Handling Noise Data with PCA Method and Optimization Using Hybrid Fuzzy C-Means and Genetic Algorithm. JUITA: Jurnal Informatika. Retrieved from https://jurnalnasional.ump.ac.id/index.php/JUITA/article/view/21765