Kartika Nur Savira (24031554049)
Tara Tabriza Rachman (24031554107)
Dataset: https://www.kaggle.com/datasets/valakhorasani/gym-members-exercise-dataset
Notebook ini membandingkan 5 algoritma clustering pada dataset gym members: K-means, K-medians, DBSCAN, Mean Shift, dan Fuzzy C-means. Data diproses menggunakan PCA sebelum clustering untuk mereduksi dimensi dan menghilangkan multikolinearitas antar fitur.
library(psych)
library(tidyverse)
library(flexclust)
library(dbscan)
library(meanShiftR)
library(e1071)
library(cluster)
library(fpc)
library(mclust)
library(factoextra)
library(gridExtra)
# Import Data
data <- read.csv("gym_members_exercise_tracking.csv")
knitr::kable(head(data))
| Age | Gender | Weight..kg. | Height..m. | Max_BPM | Avg_BPM | Resting_BPM | Session_Duration..hours. | Calories_Burned | Workout_Type | Fat_Percentage | Water_Intake..liters. | Workout_Frequency..days.week. | Experience_Level | BMI |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 56 | Male | 88.3 | 1.71 | 180 | 157 | 60 | 1.69 | 1313 | Yoga | 12.6 | 3.5 | 4 | 3 | 30.20 |
| 46 | Female | 74.9 | 1.53 | 179 | 151 | 66 | 1.30 | 883 | HIIT | 33.9 | 2.1 | 4 | 2 | 32.00 |
| 32 | Female | 68.1 | 1.66 | 167 | 122 | 54 | 1.11 | 677 | Cardio | 33.4 | 2.3 | 4 | 2 | 24.71 |
| 25 | Male | 53.2 | 1.70 | 190 | 164 | 56 | 0.59 | 532 | Strength | 28.8 | 2.1 | 3 | 1 | 18.41 |
| 38 | Male | 46.1 | 1.79 | 188 | 158 | 68 | 0.64 | 556 | Strength | 29.2 | 2.8 | 3 | 1 | 14.39 |
| 56 | Female | 58.0 | 1.68 | 168 | 156 | 74 | 1.59 | 1116 | HIIT | 15.5 | 2.7 | 5 | 3 | 20.55 |
# Dimensi Data
cat("Dimensi:", nrow(data), "x", ncol(data), "\n")
## Dimensi: 973 x 15
# Struktur dan Tipe Data
str(data)
## 'data.frame': 973 obs. of 15 variables:
## $ Age : int 56 46 32 25 38 56 36 40 28 28 ...
## $ Gender : chr "Male" "Female" "Female" "Male" ...
## $ Weight..kg. : num 88.3 74.9 68.1 53.2 46.1 ...
## $ Height..m. : num 1.71 1.53 1.66 1.7 1.79 1.68 1.72 1.51 1.94 1.84 ...
## $ Max_BPM : int 180 179 167 190 188 168 174 189 185 169 ...
## $ Avg_BPM : int 157 151 122 164 158 156 169 141 127 136 ...
## $ Resting_BPM : int 60 66 54 56 68 74 73 64 52 64 ...
## $ Session_Duration..hours. : num 1.69 1.3 1.11 0.59 0.64 1.59 1.49 1.27 1.03 1.08 ...
## $ Calories_Burned : num 1313 883 677 532 556 ...
## $ Workout_Type : chr "Yoga" "HIIT" "Cardio" "Strength" ...
## $ Fat_Percentage : num 12.6 33.9 33.4 28.8 29.2 15.5 21.3 30.6 28.9 29.7 ...
## $ Water_Intake..liters. : num 3.5 2.1 2.3 2.1 2.8 2.7 2.3 1.9 2.6 2.7 ...
## $ Workout_Frequency..days.week.: int 4 4 4 3 3 5 3 3 4 3 ...
## $ Experience_Level : int 3 2 2 1 1 3 2 2 2 1 ...
## $ BMI : num 30.2 32 24.7 18.4 14.4 ...
summary(data)
## Age Gender Weight..kg. Height..m.
## Min. :18.00 Length:973 Min. : 40.00 Min. :1.500
## 1st Qu.:28.00 Class :character 1st Qu.: 58.10 1st Qu.:1.620
## Median :40.00 Mode :character Median : 70.00 Median :1.710
## Mean :38.68 Mean : 73.85 Mean :1.723
## 3rd Qu.:49.00 3rd Qu.: 86.00 3rd Qu.:1.800
## Max. :59.00 Max. :129.90 Max. :2.000
## Max_BPM Avg_BPM Resting_BPM Session_Duration..hours.
## Min. :160.0 Min. :120.0 Min. :50.00 Min. :0.500
## 1st Qu.:170.0 1st Qu.:131.0 1st Qu.:56.00 1st Qu.:1.040
## Median :180.0 Median :143.0 Median :62.00 Median :1.260
## Mean :179.9 Mean :143.8 Mean :62.22 Mean :1.256
## 3rd Qu.:190.0 3rd Qu.:156.0 3rd Qu.:68.00 3rd Qu.:1.460
## Max. :199.0 Max. :169.0 Max. :74.00 Max. :2.000
## Calories_Burned Workout_Type Fat_Percentage Water_Intake..liters.
## Min. : 303.0 Length:973 Min. :10.00 Min. :1.500
## 1st Qu.: 720.0 Class :character 1st Qu.:21.30 1st Qu.:2.200
## Median : 893.0 Mode :character Median :26.20 Median :2.600
## Mean : 905.4 Mean :24.98 Mean :2.627
## 3rd Qu.:1076.0 3rd Qu.:29.30 3rd Qu.:3.100
## Max. :1783.0 Max. :35.00 Max. :3.700
## Workout_Frequency..days.week. Experience_Level BMI
## Min. :2.000 Min. :1.00 Min. :12.32
## 1st Qu.:3.000 1st Qu.:1.00 1st Qu.:20.11
## Median :3.000 Median :2.00 Median :24.16
## Mean :3.322 Mean :1.81 Mean :24.91
## 3rd Qu.:4.000 3rd Qu.:2.00 3rd Qu.:28.56
## Max. :5.000 Max. :3.00 Max. :49.84
Interpretasi:
Dataset terdiri dari 973 anggota gym dengan 15 variabel (13 numerik, 2 kategorikal:
GenderdanWorkout_Type).Fitur numerik yang tersedia mencakup data fisiologis (Age, Weight, Height, BMI, Fat_Percentage, BPM) dan perilaku latihan (Session_Duration, Calories_Burned, Water_Intake, Workout_Frequency, Experience_Level).
colSums(is.na(data))
## Age Gender
## 0 0
## Weight..kg. Height..m.
## 0 0
## Max_BPM Avg_BPM
## 0 0
## Resting_BPM Session_Duration..hours.
## 0 0
## Calories_Burned Workout_Type
## 0 0
## Fat_Percentage Water_Intake..liters.
## 0 0
## Workout_Frequency..days.week. Experience_Level
## 0 0
## BMI
## 0
data_num <- data[, sapply(data, is.numeric)]
cat("Fitur numerik yang digunakan:", ncol(data_num), "\n")
## Fitur numerik yang digunakan: 13
colnames(data_num) <- paste0("X", 1:ncol(data_num))
knitr::kable(head(data_num))
| X1 | X2 | X3 | X4 | X5 | X6 | X7 | X8 | X9 | X10 | X11 | X12 | X13 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 56 | 88.3 | 1.71 | 180 | 157 | 60 | 1.69 | 1313 | 12.6 | 3.5 | 4 | 3 | 30.20 |
| 46 | 74.9 | 1.53 | 179 | 151 | 66 | 1.30 | 883 | 33.9 | 2.1 | 4 | 2 | 32.00 |
| 32 | 68.1 | 1.66 | 167 | 122 | 54 | 1.11 | 677 | 33.4 | 2.3 | 4 | 2 | 24.71 |
| 25 | 53.2 | 1.70 | 190 | 164 | 56 | 0.59 | 532 | 28.8 | 2.1 | 3 | 1 | 18.41 |
| 38 | 46.1 | 1.79 | 188 | 158 | 68 | 0.64 | 556 | 29.2 | 2.8 | 3 | 1 | 14.39 |
| 56 | 58.0 | 1.68 | 168 | 156 | 74 | 1.59 | 1116 | 15.5 | 2.7 | 5 | 3 | 20.55 |
Interpretasi:
Terdapat 13 fitur numerik yang digunakan dan tidak ditemukan missing values pada seluruh kolom, sehingga data siap diproses tanpa perlu imputasi.
Nama-nama fitur diganti menjadi X1 - X13 agar lebih mudah ketika melakukan proses lebih lanjut.
Masing-masing variabel memiliki korelasi dan tidak bernilai 0
cor(data_num)
## X1 X2 X3 X4 X5
## X1 1.000000000 -0.036339635 -0.027837495 -0.0170725970 0.0359691433
## X2 -0.036339635 1.000000000 0.365321203 0.0570611305 0.0097174780
## X3 -0.027837495 0.365321203 1.000000000 -0.0176598843 -0.0147762881
## X4 -0.017072597 0.057061130 -0.017659884 1.0000000000 -0.0397514432
## X5 0.035969143 0.009717478 -0.014776288 -0.0397514432 1.0000000000
## X6 0.004353714 -0.032138091 -0.005089864 0.0366474807 0.0596355022
## X7 -0.019911904 -0.013665561 -0.010205897 0.0100509814 0.0160144382
## X8 -0.154678760 0.095443473 0.086348051 0.0020900159 0.3396586672
## X9 0.002370051 -0.225511640 -0.235520936 -0.0090557315 -0.0073016551
## X10 0.041528359 0.394275710 0.393532902 0.0316206428 -0.0029106374
## X11 0.008055163 -0.011769328 -0.011269883 -0.0290990657 -0.0106807977
## X12 -0.018675927 0.003378528 -0.010266611 0.0005448337 -0.0008881572
## X13 -0.013691370 0.853157690 -0.159468750 0.0671052310 0.0216054995
## X6 X7 X8 X9 X10
## X1 0.004353714 -0.019911904 -0.154678760 0.002370051 0.041528359
## X2 -0.032138091 -0.013665561 0.095443473 -0.225511640 0.394275710
## X3 -0.005089864 -0.010205897 0.086348051 -0.235520936 0.393532902
## X4 0.036647481 0.010050981 0.002090016 -0.009055731 0.031620643
## X5 0.059635502 0.016014438 0.339658667 -0.007301655 -0.002910637
## X6 1.000000000 -0.016648808 0.016517951 -0.016834389 0.007725998
## X7 -0.016648808 1.000000000 0.908140376 -0.581519771 0.283410977
## X8 0.016517951 0.908140376 1.000000000 -0.597615248 0.356930683
## X9 -0.016834389 -0.581519771 -0.597615248 1.000000000 -0.588682834
## X10 0.007725998 0.283410977 0.356930683 -0.588682834 1.000000000
## X11 -0.007966891 0.644140366 0.576150125 -0.537059548 0.238562571
## X12 0.001757585 0.764768119 0.694129448 -0.654362613 0.304103549
## X13 -0.032542632 -0.006492647 0.059760826 -0.119257760 0.213696572
## X11 X12 X13
## X1 0.008055163 -0.0186759269 -0.013691370
## X2 -0.011769328 0.0033785279 0.853157690
## X3 -0.011269883 -0.0102666112 -0.159468750
## X4 -0.029099066 0.0005448337 0.067105231
## X5 -0.010680798 -0.0008881572 0.021605500
## X6 -0.007966891 0.0017575852 -0.032542632
## X7 0.644140366 0.7647681189 -0.006492647
## X8 0.576150125 0.6941294479 0.059760826
## X9 -0.537059548 -0.6543626129 -0.119257760
## X10 0.238562571 0.3041035494 0.213696572
## X11 1.000000000 0.8370787094 0.001644974
## X12 0.837078709 1.0000000000 0.016031073
## X13 0.001644974 0.0160310726 1.000000000
corrplot::corrplot(cor(data_num), tl.col = "black", tl.srt = 45, tl.cex = 0.5)
KMO/MSA memilki nilai > 0.5
# Check MSA
r <- cor(data_num)
KMO(r)
## Kaiser-Meyer-Olkin factor adequacy
## Call: KMO(r = r)
## Overall MSA = 0.47
## MSA for each item =
## X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13
## 0.02 0.35 0.16 0.63 0.06 0.25 0.50 0.49 0.86 0.75 0.80 0.79 0.29
# Delete X1
data <- data_num[-1]
r <- cor(data)
KMO(r)
## Kaiser-Meyer-Olkin factor adequacy
## Call: KMO(r = r)
## Overall MSA = 0.54
## MSA for each item =
## X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13
## 0.36 0.16 0.60 0.09 0.40 0.59 0.58 0.87 0.82 0.80 0.80 0.30
# Delete X5
data <- data[-4]
r <- cor(data)
KMO(r)
## Kaiser-Meyer-Olkin factor adequacy
## Call: KMO(r = r)
## Overall MSA = 0.61
## MSA for each item =
## X2 X3 X4 X6 X7 X8 X9 X10 X11 X12 X13
## 0.36 0.16 0.54 0.19 0.74 0.75 0.87 0.83 0.80 0.79 0.30
# Delete X3
data <- data[-2]
r <- cor(data)
KMO(r)
## Kaiser-Meyer-Olkin factor adequacy
## Call: KMO(r = r)
## Overall MSA = 0.72
## MSA for each item =
## X2 X4 X6 X7 X8 X9 X10 X11 X12 X13
## 0.51 0.53 0.19 0.74 0.75 0.85 0.73 0.80 0.78 0.50
# Delete X6
data <- data[-3]
r <- cor(data)
KMO(r)
## Kaiser-Meyer-Olkin factor adequacy
## Call: KMO(r = r)
## Overall MSA = 0.72
## MSA for each item =
## X2 X4 X7 X8 X9 X10 X11 X12 X13
## 0.51 0.56 0.74 0.75 0.85 0.73 0.80 0.79 0.50
Dengan syarat hasil p-value < 0.05
bartlett.test(data)
##
## Bartlett test of homogeneity of variances
##
## data: data
## Bartlett's K-squared = 53948, df = 8, p-value < 2.2e-16
Metode principal component analysis atau PCA merupakan suatu teknik multivariat yang bertujuan untuk mereduksi faktor atau variabel dalam jumlah besar menjadi beberapa faktor yang lebih sedikit.
Scaling Z-score diperlukan karena rentang antar fitur sangat berbeda
— misalnya Calories_Burned bisa mencapai ribuan, sementara
Height (m) hanya berkisar 1.5–2.0. Tanpa scaling, fitur
dengan rentang besar akan mendominasi perhitungan jarak dan membuat
clustering menjadi bias.
data_scale <- scale(data)
r = cov(data_scale)
pc <- eigen(r)
eigen_value <- pc$values
eigen_vector <- pc$vectors
print(eigen_value)
## [1] 3.97241779 2.01436367 0.99301511 0.82277961 0.55528999 0.30782414 0.14308312
## [8] 0.11605509 0.07517148
print(eigen_vector)
## [,1] [,2] [,3] [,4] [,5]
## [1,] -0.099617240 -0.65940372 -0.06538785 -0.14384144 -0.008399245
## [2,] -0.005696882 -0.09097688 0.99424603 0.02099783 0.044865893
## [3,] -0.440116953 0.15513574 0.04407065 -0.18577363 -0.440966682
## [4,] -0.435887810 0.07399296 0.02110284 -0.12288184 -0.571382876
## [5,] 0.408796215 0.08320719 0.02566220 -0.36646008 -0.141992160
## [6,] -0.271758072 -0.29072163 -0.04086506 0.73988720 0.001629442
## [7,] -0.400789286 0.15495238 -0.02254232 -0.23976481 0.589730005
## [8,] -0.446212227 0.14249754 0.01358778 -0.16818811 0.329125078
## [9,] -0.075090869 -0.62578718 -0.04231333 -0.40218961 0.026782003
## [,6] [,7] [,8] [,9]
## [1,] -0.002632990 0.35104856 0.6091448359 0.189544735
## [2,] -0.001620776 0.02530538 0.0008635083 -0.008245426
## [3,] -0.067321913 -0.10161042 -0.1160223900 0.723191661
## [4,] -0.073596872 0.24264945 0.0301603762 -0.630316021
## [5,] -0.796334024 -0.12359334 0.1450612238 -0.019569357
## [6,] -0.509513725 -0.14481995 -0.1091713262 0.005232390
## [7,] -0.308731214 0.50252965 -0.2472519228 0.024420993
## [8,] -0.008113606 -0.63480224 0.4634801799 -0.161059984
## [9,] 0.030146211 -0.33935681 -0.5528674818 -0.129434537
sumvar <- sum(eigen_value)
propvar <- eigen_value / sumvar
cumvar <- cumsum(propvar)
# Tampilkan tabel
pca_table <- data.frame(
PC = paste0("PC", 1:length(eigen_value)),
eigen_value = eigen_value,
propvar = propvar * 100,
cumulative = cumvar * 100
)
print(pca_table)
## PC eigen_value propvar cumulative
## 1 PC1 3.97241779 44.1379754 44.13798
## 2 PC2 2.01436367 22.3818185 66.51979
## 3 PC3 0.99301511 11.0335012 77.55330
## 4 PC4 0.82277961 9.1419956 86.69529
## 5 PC5 0.55528999 6.1698887 92.86518
## 6 PC6 0.30782414 3.4202683 96.28545
## 7 PC7 0.14308312 1.5898125 97.87526
## 8 PC8 0.11605509 1.2895010 99.16476
## 9 PC9 0.07517148 0.8352387 100.00000
n_pc <- which(cumvar >= 0.80)[1]
# Visualisasi
par(mfrow = c(1, 2), mar = c(5, 4, 4, 2))
# Scree Plot
plot(propvar * 100, type = "b", pch = 19, col = "steelblue", lwd = 2,
xlab = "Principal Component", ylab = "Varians (%)",
main = "Scree Plot", frame = FALSE)
abline(v = n_pc, col = "red", lty = 2)
legend("topright", legend = paste("PC ke-", n_pc),
col = "red", lty = 2, bty = "n")
# Kumulatif Varians
plot(cumvar * 100, type = "b", pch = 19, col = "darkorange", lwd = 2,
xlab = "Jumlah PC", ylab = "Kumulatif Varians (%)",
main = "Kumulatif Varians", frame = FALSE, ylim = c(0, 100))
abline(h = 80, col = "red", lty = 2)
legend("bottomright", legend = "80% threshold",
col = "red", lty = 2, bty = "n")
Interpretasi:
Berdasarkan scree plot, terlihat bahwa terjadi penurunan variansi yang signifikan dari PC1 hingga PC3, kemudian kurva mulai melandai setelah PC ke-4. Hal ini menunjukkan adanya titik elbow di sekitar PC ke-3 hingga PC ke-4.
Pada grafik kumulatif varians, terlihat bahwa total variansi yang dijelaskan mencapai lebih dari 80% pada PC ke-4. Namun, dalam analisis clustering ini digunakan seluruh komponen utama.
Hal ini dilakukan karena jumlah variabel awal tidak terlalu besar, sehingga reduksi dimensi tidak menjadi kebutuhan utama. PCA tetap digunakan untuk mentransformasi data ke ruang yang bebas korelasi (orthogonal), sehingga dapat meningkatkan kinerja algoritma clustering yang sensitif terhadap korelasi antar variabel.
scores <- data_scale %*% eigen_vector
# Ambil sesuai n_pc
scores_PC <- scores
head(scores_PC)
## [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] -3.5548957 -0.8833618 -0.08595574 0.517010543 -0.1447401 0.4177351
## [2,] 0.3043330 -0.1523520 -0.06144534 -1.843146738 0.3344867 -0.8900295
## [3,] 0.8742913 0.5933339 -1.08535378 -0.907137050 0.9490050 -0.9337736
## [4,] 2.7352589 0.8658108 0.90771702 0.475913884 1.0044088 0.2816337
## [5,] 2.4215596 1.1754000 0.74487711 1.564798773 0.8615264 -0.3968399
## [6,] -2.7419137 1.5571772 -0.95281782 0.007667743 0.9010904 0.4243947
## [,7] [,8] [,9]
## [1,] -0.41012131 -0.009206248 -0.19661338
## [2,] -0.21756741 -0.337534476 -0.03979506
## [3,] -0.15051135 0.064842298 0.12866605
## [4,] 0.44619488 -0.104668701 -0.45596013
## [5,] 0.35883015 -0.107400094 -0.38509153
## [6,] 0.09407731 -0.125485492 -0.01599479
pca_res <- prcomp(data_scale, center = FALSE, scale. = FALSE)
fviz_pca_var(pca_res,
axes = c(1, 2),
col.var = "contrib",
gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
repel = TRUE) +
labs(title = "PCA — Kontribusi Variabel ke PC1 & PC2") +
theme_minimal(base_size = 11)
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## ℹ The deprecated feature was likely used in the ggpubr package.
## Please report the issue at <https://github.com/kassambara/ggpubr/issues>.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
## ℹ Please use tidy evaluation idioms with `aes()`.
## ℹ See also `vignette("ggplot2-in-packages")` for more information.
## ℹ The deprecated feature was likely used in the factoextra package.
## Please report the issue at <https://github.com/kassambara/factoextra/issues>.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
Interpretasi:
Berdasarkan plot kontribusi variabel terhadap PC1 dan PC2, terlihat bahwa variabel X2, X13, dan X9 memiliki kontribusi terbesar, ditunjukkan oleh panjang panah dan warna yang lebih intens.
Pada PC1 (44.1%), variabel X9 memiliki arah berlawanan dengan kelompok variabel X7, X11, X12, dan X8, yang menunjukkan adanya korelasi negatif di antara variabel-variabel tersebut. Hal ini mengindikasikan bahwa PC1 berperan dalam membedakan dua kelompok karakteristik yang berlawanan.
Pada PC2 (22.4%), variabel X2 dan X13 mendominasi kontribusi, menunjukkan bahwa dimensi ini terutama merepresentasikan variasi dari kedua variabel tersebut.
Selain itu, variabel yang memiliki arah panah yang searah, seperti X2 dan X13, menunjukkan korelasi positif yang kuat, sedangkan variabel yang berlawanan arah menunjukkan korelasi negatif.
# Elbow Method (WSS)
wss <- sapply(1:10, function(k) {
kmeans(scores_PC, centers = k, nstart = 20, iter.max = 100)$tot.withinss
})
# Silhouette Method
k_values <- 2:10
avg_sil_values <- sapply(k_values, function(k) {
km <- kmeans(scores_PC, centers = k, nstart = 25, iter.max = 100)
mean(silhouette(km$cluster, dist(scores_PC))[, 3])
})
# Best K
best_k <- k_values[which.max(avg_sil_values)]
cat("K optimal (Silhouette):", best_k, "\n")
## K optimal (Silhouette): 2
par(mfrow = c(1, 2), mar = c(5, 4, 4, 2))
plot(1:10, wss, type = "b", pch = 19, col = "steelblue", lwd = 2,
xlab = "K", ylab = "Within-Cluster SS", main = "Elbow Method", frame = FALSE)
abline(v = 3, col = "red", lty = 2)
legend("topright", legend = "K = 3", col = "red", lty = 2, bty = "n")
plot(k_values, avg_sil_values, type = "b", pch = 19, col = "darkorange", lwd = 2,
xlab = "K", ylab = "Avg Silhouette Width", main = "Silhouette Analysis", frame = FALSE)
abline(v = best_k, col = "red", lty = 2)
legend("topright", legend = paste("K =", best_k), col = "red", lty = 2, bty = "n")
Interpretasi:
Elbow Method: Terjadi penurunan nilai Within-Cluster Sum of Squares (WSS) yang cukup tajam dari K=1 hingga K=3, setelah itu penurunannya mulai melandai. Pola ini menunjukkan adanya titik “siku” (elbow) pada K=3, sehingga jumlah cluster optimal yang disarankan adalah 3.
Silhouette Analysis: Nilai rata-rata silhouette tertinggi diperoleh pada K=2 (sekitar 0.33), kemudian menurun pada K=3 (sekitar 0.29) dan terus menurun untuk K yang lebih besar. Hal ini menunjukkan bahwa secara kualitas pemisahan cluster, K=2 memberikan struktur yang paling kompak dan terpisah.
Pemilihan jumlah cluster: Meskipun K=2 memiliki nilai silhouette tertinggi, K=3 tetap dipilih karena:
- didukung oleh hasil Elbow Method, dan
- memberikan segmentasi yang lebih kaya serta informatif untuk analisis lanjutan.
Kualitas cluster: Nilai silhouette yang relatif rendah hingga sedang (< 0.35) mengindikasikan bahwa pemisahan antar cluster tidak terlalu kuat. Hal ini menunjukkan bahwa data memiliki pola yang cenderung kontinu, sehingga batas antar kelompok tidak terbentuk secara sangat jelas.
# 1. K-means
km_res <- kmeans(scores_PC, centers = 3)
km_res
## K-means clustering with 3 clusters of sizes 560, 195, 218
##
## Cluster means:
## [,1] [,2] [,3] [,4] [,5] [,6]
## 1 1.0705090 0.6820788 0.005338244 -0.05584093 -0.02869078 -0.03795239
## 2 -3.3165870 0.4047151 0.052234680 0.20374952 0.13942444 0.24119045
## 3 0.2167406 -2.1141447 -0.060436601 -0.03880843 -0.05101343 -0.11825139
## [,7] [,8] [,9]
## 1 0.01311168 -0.01544317 -0.02032968
## 2 -0.08690642 -0.03652687 0.03078970
## 3 0.04405603 0.07234365 0.02468180
##
## Clustering vector:
## [1] 2 1 1 1 1 2 1 1 3 3 3 1 3 3 3 1 1 1 1 1 3 1 1 3 1 1 1 1 2 1 3 1 1 1 2 3 1
## [38] 3 1 1 1 1 3 1 3 1 1 3 1 3 1 2 3 3 1 3 1 1 2 1 1 1 2 1 2 1 2 1 2 3 2 1 1 1
## [75] 3 1 1 1 3 2 1 2 1 1 1 1 3 1 3 1 2 2 3 1 3 1 3 3 1 2 3 1 1 3 1 2 1 2 1 1 1
## [112] 1 2 2 1 1 1 2 3 1 1 3 3 3 2 3 1 2 3 2 1 1 2 3 1 3 1 1 1 2 3 1 3 1 1 2 1 1
## [149] 3 1 1 3 2 1 3 1 1 1 1 1 3 3 3 2 2 1 2 1 1 1 3 1 2 1 3 1 2 1 1 1 3 2 2 3 2
## [186] 1 2 3 1 1 1 1 1 3 1 1 1 2 1 1 1 2 1 1 2 2 3 1 1 2 1 1 2 1 1 1 1 1 1 3 1 2
## [223] 1 2 3 2 3 1 1 2 1 1 1 2 3 1 3 2 1 1 3 3 1 1 3 1 1 1 1 1 3 3 1 1 1 1 2 3 1
## [260] 3 1 3 1 1 3 1 1 2 1 2 1 3 1 1 2 3 2 1 2 3 1 1 1 3 1 2 3 3 3 3 1 3 3 1 1 1
## [297] 1 1 3 3 3 2 3 2 1 1 1 2 1 2 2 3 3 1 1 2 2 1 1 1 1 2 1 1 1 1 1 1 1 1 2 1 1
## [334] 1 1 3 1 1 3 1 3 2 1 1 3 3 1 1 2 1 1 1 1 1 1 3 2 3 1 3 1 3 1 1 2 1 1 2 1 3
## [371] 3 1 1 2 1 3 3 1 1 3 2 3 3 1 3 1 1 1 3 1 1 2 1 1 1 2 2 1 2 1 1 1 1 1 2 3 3
## [408] 1 3 1 1 1 2 1 3 1 1 1 1 2 3 2 2 3 2 1 2 3 2 2 2 1 1 1 1 1 3 1 2 1 2 1 2 2
## [445] 1 3 3 1 3 1 1 1 1 1 1 2 3 2 3 1 1 1 1 3 1 3 1 3 1 1 3 1 2 1 1 2 3 1 1 1 3
## [482] 3 1 1 2 2 3 1 1 1 1 1 2 1 3 3 3 2 1 1 1 1 1 1 1 1 1 3 1 1 1 2 2 2 1 1 1 3
## [519] 2 1 1 1 1 1 3 1 3 1 3 2 2 1 1 1 3 1 2 1 2 3 1 1 2 1 1 1 1 3 1 1 1 3 1 1 3
## [556] 1 1 2 1 1 1 3 3 1 3 1 1 1 1 1 1 3 2 1 3 1 1 1 1 1 1 1 1 1 2 1 1 2 3 3 1 2
## [593] 1 3 3 2 1 1 1 1 1 2 1 3 1 2 1 1 2 2 2 2 1 1 3 2 1 1 3 1 1 2 2 1 1 1 1 2 1
## [630] 1 2 3 1 1 3 1 1 1 2 3 3 1 1 3 3 3 2 3 3 2 1 1 1 1 1 1 1 1 1 1 1 3 1 2 1 1
## [667] 1 1 2 2 1 1 2 2 1 1 3 1 3 1 1 3 1 1 1 1 1 3 1 1 1 1 2 1 2 1 3 3 1 1 1 1 1
## [704] 1 1 1 3 1 1 1 3 2 2 1 3 3 1 1 3 2 1 3 1 1 3 1 1 1 2 3 1 1 2 3 3 2 2 3 2 3
## [741] 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 2 1 1 3 1 1 3 1 1 1 2 1 1 1 3 2 3 1
## [778] 2 1 2 1 1 1 1 2 3 1 1 2 1 2 1 1 1 1 1 1 2 1 1 1 2 1 1 2 1 3 1 1 1 2 2 3 1
## [815] 1 3 1 1 1 2 1 1 1 1 1 1 3 1 1 1 2 2 2 3 1 2 1 1 3 3 2 1 1 1 3 2 3 1 2 3 1
## [852] 1 3 1 3 3 1 2 1 1 1 1 1 2 2 3 2 1 3 1 2 1 1 1 1 3 2 2 1 2 1 2 1 1 3 2 1 1
## [889] 1 3 1 3 2 1 1 1 2 2 3 1 3 1 2 1 1 3 2 3 1 1 2 3 3 3 3 1 3 1 1 1 2 1 2 1 3
## [926] 1 3 2 1 1 2 1 2 3 1 1 3 3 1 1 1 1 2 2 1 3 1 1 2 3 1 1 1 1 2 3 1 2 1 3 1 1
## [963] 2 1 2 1 2 1 2 1 2 3 3
##
## Within cluster sum of squares by cluster:
## [1] 2805.7844 654.9323 1187.8856
## (between_SS / total_SS = 46.9 %)
##
## Available components:
##
## [1] "cluster" "centers" "totss" "withinss" "tot.withinss"
## [6] "betweenss" "size" "iter" "ifault"
# Tambahin cluster ke data asli
data_clustered <- as.data.frame(scores_PC) # Convert matrix to data frame
data_clustered$cluster <- km_res$cluster
# Distribusi cluster
data_clustered %>%
dplyr::count(cluster) %>%
knitr::kable(col.names = c("Cluster", "Jumlah"),
caption = "Distribusi Cluster (K-Means)")
| Cluster | Jumlah |
|---|---|
| 1 | 560 |
| 2 | 195 |
| 3 | 218 |
cluster1 <- subset(data_clustered, cluster == 1)
cluster2 <- subset(data_clustered, cluster == 2)
cluster3 <- subset(data_clustered, cluster == 3)
knitr::kable(head(cluster1, 10), caption = "Data Cluster 1")
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | cluster | |
|---|---|---|---|---|---|---|---|---|---|---|
| 2 | 0.3043330 | -0.1523520 | -0.0614453 | -1.8431467 | 0.3344867 | -0.8900295 | -0.2175674 | -0.3375345 | -0.0397951 | 1 |
| 3 | 0.8742913 | 0.5933339 | -1.0853538 | -0.9071370 | 0.9490050 | -0.9337736 | -0.1505114 | 0.0648423 | 0.1286660 | 1 |
| 4 | 2.7352589 | 0.8658108 | 0.9077170 | 0.4759139 | 1.0044088 | 0.2816337 | 0.4461949 | -0.1046687 | -0.4559601 | 1 |
| 5 | 2.4215596 | 1.1754000 | 0.7448771 | 1.5647988 | 0.8615264 | -0.3968399 | 0.3588302 | -0.1074001 | -0.3850915 | 1 |
| 7 | -1.0995397 | 0.5923718 | -0.4035556 | -0.4058413 | -1.3721097 | 0.6724907 | 0.1557596 | 0.1475879 | -0.6628256 | 1 |
| 8 | 0.6732408 | -0.0622624 | 0.8481589 | -1.4832362 | -0.1884369 | 0.0331401 | -0.6262349 | -0.1252764 | -0.1747995 | 1 |
| 12 | -0.0720606 | 0.8687009 | -0.3788614 | 1.8397674 | 0.0934775 | -0.4561576 | -0.5479242 | -0.0068691 | -0.2270595 | 1 |
| 16 | 2.0848219 | 1.7915295 | 0.2926179 | 0.8342424 | -0.7615915 | 0.7705099 | 0.0062253 | 0.0142321 | 0.1818240 | 1 |
| 17 | 0.0921168 | 0.4813353 | -1.1038968 | -0.8813410 | -0.7992718 | -0.2854199 | -0.5127807 | -0.1103230 | -0.5134098 | 1 |
| 18 | 1.9295951 | 2.2877142 | 0.4258162 | -0.4888902 | -0.6214933 | 0.0811903 | 0.6937203 | -0.0087689 | -0.0110665 | 1 |
knitr::kable(head(cluster2, 10), caption = "Data Cluster 2")
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | cluster | |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | -3.554896 | -0.8833618 | -0.0859557 | 0.5170105 | -0.1447401 | 0.4177351 | -0.4101213 | -0.0092062 | -0.1966134 | 2 |
| 6 | -2.741914 | 1.5571772 | -0.9528178 | 0.0076677 | 0.9010904 | 0.4243947 | 0.0940773 | -0.1254855 | -0.0159948 | 2 |
| 29 | -3.414256 | -0.1816066 | 0.4373335 | 0.6577629 | 0.9690840 | -0.0837603 | 0.1827329 | 0.1234493 | 0.2782091 | 2 |
| 35 | -3.755421 | -0.4620553 | 0.4919035 | 0.7232916 | 0.9538967 | 0.4280043 | 0.1917607 | -0.2883135 | -0.3658760 | 2 |
| 52 | -3.606637 | -0.2392797 | -1.3884533 | 0.5174930 | 0.8306471 | 0.0956225 | 0.0351037 | -0.1567395 | 0.2362544 | 2 |
| 59 | -2.333756 | 1.2384319 | 0.2044168 | 0.0615809 | 0.0795927 | 0.4442417 | -0.4327536 | 0.2367311 | 0.0516105 | 2 |
| 63 | -4.057361 | -0.9416178 | 0.1411246 | 0.0567164 | 0.4695292 | 0.0454726 | -0.0416330 | -0.5442443 | -0.0742905 | 2 |
| 65 | -3.640339 | 1.5365387 | -1.5940738 | -0.6020632 | -0.2878593 | 0.0474571 | 0.2149893 | -0.1392534 | 0.1106202 | 2 |
| 67 | -4.717246 | -0.6868947 | -0.4020405 | -0.3360717 | -0.5029764 | -0.2789369 | 0.0385609 | -0.6544218 | -0.2604822 | 2 |
| 69 | -2.785245 | 1.4096767 | 0.9388239 | -0.0720908 | -0.5279174 | 0.2525443 | -0.2276002 | 0.3290233 | -0.1192882 | 2 |
knitr::kable(head(cluster3, 10), caption = "Data Cluster 3")
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | cluster | |
|---|---|---|---|---|---|---|---|---|---|---|
| 9 | 0.1333676 | -2.1620845 | 0.2077551 | -1.0409090 | 1.1462374 | -0.5862504 | 0.4650611 | 0.8452888 | 0.1975062 | 3 |
| 10 | 1.1030672 | -1.5571670 | -1.0805812 | -0.2987946 | -0.2768027 | -0.4632111 | 0.5484607 | 0.0984137 | 0.1642210 | 3 |
| 11 | 1.2347599 | -4.1550594 | 0.3322366 | 0.2162707 | 0.1912985 | 0.9546327 | -0.3253190 | -0.3861987 | 0.0167115 | 3 |
| 13 | -0.7429353 | -3.1278271 | 1.0682344 | -1.0903531 | -0.1441856 | 0.4613117 | -0.4695494 | -0.2797030 | -0.3483750 | 3 |
| 14 | 0.5092599 | -1.2740350 | -0.2193996 | 1.4662842 | -0.3097752 | -1.0905298 | 0.5362785 | 0.1589099 | 0.4756315 | 3 |
| 15 | -1.1066001 | -2.7299374 | 1.1909131 | -0.3264263 | -0.7911292 | -0.7004210 | -0.1009853 | 0.4631988 | -0.3545284 | 3 |
| 21 | -1.2908732 | -0.9281818 | -0.5181428 | 0.3532140 | -0.1862299 | -1.7134619 | -0.0097926 | -0.0911675 | 0.2286129 | 3 |
| 24 | 1.4498074 | -3.8214515 | 1.1497064 | 0.5550940 | -0.1825644 | -0.4031192 | -0.1623373 | 0.1758668 | -0.2892818 | 3 |
| 31 | 0.0936604 | -2.3692291 | -0.8859936 | 0.3513297 | -0.7937854 | -1.0425243 | 0.4065206 | -0.2165840 | 0.2055142 | 3 |
| 36 | -0.2799133 | -3.4477563 | -1.3776670 | -1.1382704 | -1.2397253 | -0.4164053 | 0.5743305 | -0.3372011 | -0.1510106 | 3 |
Interpretasi K-Means:
Metode K-Means membagi data menjadi 3 cluster berdasarkan kedekatan jarak terhadap centroid (mean) pada ruang Principal Component.
Berdasarkan hasil clustering, distribusi anggota tiap cluster adalah: - Cluster 1: 195 data - Cluster 2: 560 data - Cluster 3: 218 data
Terlihat bahwa distribusi cluster tidak seimbang, dimana Cluster 2 mendominasi dengan jumlah anggota yang jauh lebih besar dibandingkan cluster lainnya. Hal ini menunjukkan bahwa sebagian besar data memiliki karakteristik yang relatif mirip dan terkonsentrasi dalam satu kelompok utama.
Selain itu, nilai between_SS / total_SS sebesar 46.9% menunjukkan bahwa sekitar setengah dari total variasi data dapat dijelaskan oleh perbedaan antar cluster. Nilai ini tergolong cukup baik (kategori sedang), yang mengindikasikan bahwa K-Means mampu memisahkan data dengan cukup jelas, meskipun belum optimal.
Dengan demikian, dapat disimpulkan bahwa K-Means sudah berhasil membentuk struktur cluster yang cukup representatif, namun masih terdapat kemungkinan tumpang tindih antar cluster, terutama karena dominasi satu cluster besar dalam dataset.
# 2. K-Medians
kmed_res <- kcca(scores_PC, k = 3, family = kccaFamily("kmedians"))
## Found more than one class "kcca" in cache; using the first, from namespace 'flexclust'
## Also defined by 'kernlab'
## Found more than one class "kcca" in cache; using the first, from namespace 'flexclust'
## Also defined by 'kernlab'
# Distribusi cluster
data.frame(cluster = clusters(kmed_res)) %>%
dplyr::count(cluster) %>%
knitr::kable(col.names = c("Cluster", "Jumlah"),
caption = "Distribusi Cluster (K-Medians)")
| Cluster | Jumlah |
|---|---|
| 1 | 313 |
| 2 | 465 |
| 3 | 195 |
# Tambahin ke data asli
data_clustered <- as.data.frame(scores_PC) # Convert matrix to data frame
data_clustered$cluster <- clusters(kmed_res)
# Pisahin per cluster
cluster1 <- subset(data_clustered, cluster == 1)
cluster2 <- subset(data_clustered, cluster == 2)
cluster3 <- subset(data_clustered, cluster == 3)
knitr::kable(head(cluster1, 10), caption = "Data Cluster 1")
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | cluster | |
|---|---|---|---|---|---|---|---|---|---|---|
| 4 | 2.7352589 | 0.8658108 | 0.9077170 | 0.4759139 | 1.0044088 | 0.2816337 | 0.4461949 | -0.1046687 | -0.4559601 | 1 |
| 5 | 2.4215596 | 1.1754000 | 0.7448771 | 1.5647988 | 0.8615264 | -0.3968399 | 0.3588302 | -0.1074001 | -0.3850915 | 1 |
| 11 | 1.2347599 | -4.1550594 | 0.3322366 | 0.2162707 | 0.1912985 | 0.9546327 | -0.3253190 | -0.3861987 | 0.0167115 | 1 |
| 16 | 2.0848219 | 1.7915295 | 0.2926179 | 0.8342424 | -0.7615915 | 0.7705099 | 0.0062253 | 0.0142321 | 0.1818240 | 1 |
| 18 | 1.9295951 | 2.2877142 | 0.4258162 | -0.4888902 | -0.6214933 | 0.0811903 | 0.6937203 | -0.0087689 | -0.0110665 | 1 |
| 20 | 0.8981528 | -0.3838146 | 0.7549208 | -0.2434322 | -1.5880251 | 0.7416073 | 0.0791051 | -0.2221252 | 0.0544307 | 1 |
| 22 | 1.1481069 | -0.5599490 | 1.4635949 | 1.3229643 | -0.9304952 | -0.0141554 | -0.0423959 | -0.0681114 | -0.0660102 | 1 |
| 23 | 1.8663454 | -0.2563679 | 0.6652213 | -0.0104114 | -1.1373642 | -0.4372326 | -0.2320201 | -0.0174101 | 0.0296210 | 1 |
| 24 | 1.4498074 | -3.8214515 | 1.1497064 | 0.5550940 | -0.1825644 | -0.4031192 | -0.1623373 | 0.1758668 | -0.2892818 | 1 |
| 27 | 2.5897498 | -0.1101889 | 1.0253227 | 1.3285645 | 0.2892521 | 0.3005442 | -0.1696413 | 0.1721331 | -0.0353738 | 1 |
knitr::kable(head(cluster2, 10), caption = "Data Cluster 2")
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | cluster | |
|---|---|---|---|---|---|---|---|---|---|---|
| 2 | 0.3043330 | -0.1523520 | -0.0614453 | -1.8431467 | 0.3344867 | -0.8900295 | -0.2175674 | -0.3375345 | -0.0397951 | 2 |
| 3 | 0.8742913 | 0.5933339 | -1.0853538 | -0.9071370 | 0.9490050 | -0.9337736 | -0.1505114 | 0.0648423 | 0.1286660 | 2 |
| 7 | -1.0995397 | 0.5923718 | -0.4035556 | -0.4058413 | -1.3721097 | 0.6724907 | 0.1557596 | 0.1475879 | -0.6628256 | 2 |
| 8 | 0.6732408 | -0.0622624 | 0.8481589 | -1.4832362 | -0.1884369 | 0.0331401 | -0.6262349 | -0.1252764 | -0.1747995 | 2 |
| 9 | 0.1333676 | -2.1620845 | 0.2077551 | -1.0409090 | 1.1462374 | -0.5862504 | 0.4650611 | 0.8452888 | 0.1975062 | 2 |
| 10 | 1.1030672 | -1.5571670 | -1.0805812 | -0.2987946 | -0.2768027 | -0.4632111 | 0.5484607 | 0.0984137 | 0.1642210 | 2 |
| 12 | -0.0720606 | 0.8687009 | -0.3788614 | 1.8397674 | 0.0934775 | -0.4561576 | -0.5479242 | -0.0068691 | -0.2270595 | 2 |
| 13 | -0.7429353 | -3.1278271 | 1.0682344 | -1.0903531 | -0.1441856 | 0.4613117 | -0.4695494 | -0.2797030 | -0.3483750 | 2 |
| 14 | 0.5092599 | -1.2740350 | -0.2193996 | 1.4662842 | -0.3097752 | -1.0905298 | 0.5362785 | 0.1589099 | 0.4756315 | 2 |
| 15 | -1.1066001 | -2.7299374 | 1.1909131 | -0.3264263 | -0.7911292 | -0.7004210 | -0.1009853 | 0.4631988 | -0.3545284 | 2 |
knitr::kable(head(cluster3, 10), caption = "Data Cluster 3")
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | cluster | |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | -3.554896 | -0.8833618 | -0.0859557 | 0.5170105 | -0.1447401 | 0.4177351 | -0.4101213 | -0.0092062 | -0.1966134 | 3 |
| 6 | -2.741914 | 1.5571772 | -0.9528178 | 0.0076677 | 0.9010904 | 0.4243947 | 0.0940773 | -0.1254855 | -0.0159948 | 3 |
| 29 | -3.414256 | -0.1816066 | 0.4373335 | 0.6577629 | 0.9690840 | -0.0837603 | 0.1827329 | 0.1234493 | 0.2782091 | 3 |
| 35 | -3.755421 | -0.4620553 | 0.4919035 | 0.7232916 | 0.9538967 | 0.4280043 | 0.1917607 | -0.2883135 | -0.3658760 | 3 |
| 52 | -3.606637 | -0.2392797 | -1.3884533 | 0.5174930 | 0.8306471 | 0.0956225 | 0.0351037 | -0.1567395 | 0.2362544 | 3 |
| 56 | -2.011343 | -2.8808307 | -0.0426030 | -1.7177908 | -0.4698060 | 0.0546570 | 0.2282208 | -0.6582573 | -0.5387367 | 3 |
| 59 | -2.333756 | 1.2384319 | 0.2044168 | 0.0615809 | 0.0795927 | 0.4442417 | -0.4327536 | 0.2367311 | 0.0516105 | 3 |
| 63 | -4.057361 | -0.9416178 | 0.1411246 | 0.0567164 | 0.4695292 | 0.0454726 | -0.0416330 | -0.5442443 | -0.0742905 | 3 |
| 65 | -3.640339 | 1.5365387 | -1.5940738 | -0.6020632 | -0.2878593 | 0.0474571 | 0.2149893 | -0.1392534 | 0.1106202 | 3 |
| 67 | -4.717246 | -0.6868947 | -0.4020405 | -0.3360717 | -0.5029764 | -0.2789369 | 0.0385609 | -0.6544218 | -0.2604822 | 3 |
Interpretasi K-Medians:
Metode K-Medians membagi data menjadi 3 cluster dengan menggunakan median sebagai pusat cluster, sehingga lebih robust terhadap outlier dibandingkan K-Means.
Berdasarkan hasil clustering, distribusi anggota tiap cluster adalah: - Cluster 1: 195 data - Cluster 2: 475 data - Cluster 3: 303 data
Distribusi ini menunjukkan bahwa data masih tidak seimbang, namun tidak sedominan hasil K-Means. Jika dibandingkan dengan K-Means, pola clustering yang dihasilkan relatif serupa, yang mengindikasikan bahwa dataset tidak mengandung outlier ekstrem yang signifikan.
Selain itu, dari contoh data pada masing-masing cluster: - Cluster 1 cenderung memiliki nilai PC1 negatif (lebih rendah dari rata-rata), yang menunjukkan kelompok dengan karakteristik tertentu yang berbeda secara signifikan dari pusat data. - Cluster 2 berada di sekitar nilai tengah (dekat nol), sehingga dapat dianggap sebagai kelompok dengan karakteristik “rata-rata”. - Cluster 3 cenderung memiliki nilai PC1 positif, yang menunjukkan kelompok dengan karakteristik yang berlawanan dengan Cluster 1.
Secara keseluruhan, K-Medians menghasilkan pembagian cluster yang lebih stabil dan sedikit lebih seimbang dibandingkan K-Means, namun tidak memberikan perbedaan yang signifikan dalam struktur cluster.
# eps=2.0 dipilih agar DBSCAN menghasilkan cluster yang bermakna
db_res <- dbscan(scores_PC, eps = 2.0, MinPts = 5)
# Tabel distribusi
data.frame(cluster = db_res$cluster) %>%
dplyr::count(cluster) %>%
knitr::kable(col.names = c("Cluster", "Jumlah"),
caption = "Distribusi Cluster (DBSCAN)")
| Cluster | Jumlah |
|---|---|
| 0 | 1 |
| 1 | 972 |
# Jumlah noise
cat("Jumlah noise:", sum(db_res$cluster == 0))
## Jumlah noise: 1
Interpretasi DBSCAN:
Dengan parameter yang digunakan, DBSCAN menghasilkan 1 cluster utama dengan 972 data dan hanya 1 data sebagai noise (outlier).
Jumlah noise yang sangat kecil (hanya 1 dari 973 data) menunjukkan bahwa hampir seluruh data dianggap berada dalam satu kepadatan yang sama. Artinya, algoritma tidak mampu menemukan pemisahan cluster berbasis densitas dalam dataset ini.
Akibatnya, DBSCAN tidak efektif dalam mengelompokkan data ini karena hanya menghasilkan satu cluster besar tanpa segmentasi yang berarti. Hal ini juga menyebabkan metrik evaluasi seperti Silhouette tidak dapat dihitung (NA), karena tidak ada lebih dari satu cluster yang terbentuk.
ms_res <- meanShift(scores_PC)
# jumlah cluster
cat("Jumlah cluster yang terbentuk:", length(unique(ms_res$assignment)), "\n")
## Jumlah cluster yang terbentuk: 213
# tabel distribusi
data.frame(cluster = ms_res$assignment) %>%
dplyr::count(cluster) %>%
knitr::kable(col.names = c("Cluster", "Jumlah"),
caption = "Distribusi Cluster (Mean Shift)")
| Cluster | Jumlah |
|---|---|
| 1 | 101 |
| 2 | 50 |
| 3 | 68 |
| 4 | 8 |
| 5 | 4 |
| 6 | 14 |
| 7 | 66 |
| 8 | 2 |
| 9 | 1 |
| 10 | 1 |
| 11 | 1 |
| 12 | 1 |
| 13 | 4 |
| 14 | 11 |
| 15 | 31 |
| 16 | 9 |
| 17 | 8 |
| 18 | 2 |
| 19 | 11 |
| 20 | 1 |
| 21 | 4 |
| 22 | 19 |
| 23 | 1 |
| 24 | 9 |
| 25 | 10 |
| 26 | 1 |
| 27 | 3 |
| 28 | 3 |
| 29 | 1 |
| 30 | 3 |
| 31 | 18 |
| 32 | 32 |
| 33 | 1 |
| 34 | 1 |
| 35 | 31 |
| 36 | 3 |
| 37 | 17 |
| 38 | 3 |
| 39 | 5 |
| 40 | 1 |
| 41 | 5 |
| 42 | 6 |
| 43 | 2 |
| 44 | 1 |
| 45 | 8 |
| 46 | 4 |
| 47 | 3 |
| 48 | 6 |
| 49 | 7 |
| 50 | 6 |
| 51 | 5 |
| 52 | 1 |
| 53 | 1 |
| 54 | 1 |
| 55 | 15 |
| 56 | 3 |
| 57 | 3 |
| 58 | 1 |
| 59 | 2 |
| 60 | 1 |
| 61 | 1 |
| 62 | 2 |
| 63 | 1 |
| 64 | 2 |
| 65 | 4 |
| 66 | 1 |
| 67 | 19 |
| 68 | 1 |
| 69 | 9 |
| 70 | 1 |
| 71 | 6 |
| 72 | 6 |
| 73 | 9 |
| 74 | 1 |
| 75 | 1 |
| 76 | 2 |
| 77 | 1 |
| 78 | 11 |
| 79 | 1 |
| 80 | 1 |
| 81 | 2 |
| 82 | 8 |
| 83 | 6 |
| 84 | 4 |
| 85 | 2 |
| 86 | 1 |
| 87 | 1 |
| 88 | 1 |
| 89 | 5 |
| 90 | 1 |
| 91 | 1 |
| 92 | 1 |
| 93 | 5 |
| 94 | 1 |
| 95 | 5 |
| 96 | 1 |
| 97 | 1 |
| 98 | 6 |
| 99 | 1 |
| 100 | 4 |
| 101 | 1 |
| 102 | 1 |
| 103 | 1 |
| 104 | 1 |
| 105 | 10 |
| 106 | 5 |
| 107 | 6 |
| 108 | 9 |
| 109 | 6 |
| 110 | 1 |
| 111 | 2 |
| 112 | 2 |
| 113 | 1 |
| 114 | 2 |
| 115 | 15 |
| 116 | 1 |
| 117 | 1 |
| 118 | 1 |
| 119 | 1 |
| 120 | 2 |
| 121 | 3 |
| 122 | 1 |
| 123 | 8 |
| 124 | 1 |
| 125 | 2 |
| 126 | 1 |
| 127 | 4 |
| 128 | 1 |
| 129 | 1 |
| 130 | 1 |
| 131 | 1 |
| 132 | 1 |
| 133 | 1 |
| 134 | 2 |
| 135 | 1 |
| 136 | 1 |
| 137 | 1 |
| 138 | 1 |
| 139 | 1 |
| 140 | 1 |
| 141 | 3 |
| 142 | 1 |
| 143 | 1 |
| 144 | 1 |
| 145 | 1 |
| 146 | 2 |
| 147 | 2 |
| 148 | 1 |
| 149 | 1 |
| 150 | 2 |
| 151 | 1 |
| 152 | 1 |
| 153 | 1 |
| 154 | 1 |
| 155 | 1 |
| 156 | 1 |
| 157 | 1 |
| 158 | 1 |
| 159 | 1 |
| 160 | 1 |
| 161 | 1 |
| 162 | 1 |
| 163 | 1 |
| 164 | 2 |
| 165 | 2 |
| 166 | 2 |
| 167 | 2 |
| 168 | 1 |
| 169 | 1 |
| 170 | 3 |
| 171 | 1 |
| 172 | 1 |
| 173 | 4 |
| 174 | 1 |
| 175 | 2 |
| 176 | 2 |
| 177 | 2 |
| 178 | 1 |
| 179 | 2 |
| 180 | 1 |
| 181 | 1 |
| 182 | 1 |
| 183 | 2 |
| 184 | 1 |
| 185 | 1 |
| 186 | 1 |
| 187 | 1 |
| 188 | 1 |
| 189 | 1 |
| 190 | 1 |
| 191 | 1 |
| 192 | 1 |
| 193 | 1 |
| 194 | 1 |
| 195 | 1 |
| 196 | 1 |
| 197 | 1 |
| 198 | 1 |
| 199 | 1 |
| 200 | 1 |
| 201 | 1 |
| 202 | 1 |
| 203 | 1 |
| 204 | 1 |
| 205 | 1 |
| 206 | 1 |
| 207 | 1 |
| 208 | 1 |
| 209 | 1 |
| 210 | 1 |
| 211 | 1 |
| 212 | 1 |
| 213 | 1 |
Interpretasi Mean Shift: Mean Shift secara otomatis menentukan jumlah cluster berdasarkan kepadatan data. Pada dataset ini, algoritma cenderung menghasilkan cluster yang sangat banyak dan tidak seimbang, mencerminkan distribusi data yang menyebar dan tidak memiliki puncak kepadatan (modus) yang tegas.
Sama seperti DBSCAN, hasil ini menegaskan bahwa data gym members tidak memiliki struktur yang cocok untuk algoritma berbasis kepadatan. Mean Shift lebih efektif pada data dengan distribusi multimodal yang jelas, misalnya data gambar atau sensor.
fcm_res <- cmeans(scores_PC, centers = 3, m = 2, iter.max = 100)
# Distribusi cluster
data.frame(cluster = fcm_res$cluster) %>%
dplyr::count(cluster) %>%
knitr::kable(col.names = c("Cluster", "Jumlah"),
caption = "Distribusi Cluster (FCM - Hard Assignment)")
| Cluster | Jumlah |
|---|---|
| 1 | 323 |
| 2 | 437 |
| 3 | 213 |
# Membership degree
knitr::kable(round(head(fcm_res$membership, 5), 3),
caption = "Contoh Membership Degree (5 Data Pertama)")
| 1 | 2 | 3 |
|---|---|---|
| 0.085 | 0.079 | 0.837 |
| 0.443 | 0.424 | 0.133 |
| 0.429 | 0.469 | 0.101 |
| 0.424 | 0.484 | 0.093 |
| 0.417 | 0.471 | 0.112 |
Interpretasi Fuzzy C-Means:
Fuzzy C-Means menghasilkan 3 cluster dengan distribusi: - Cluster 1: 323 data - Cluster 2: 213 data - Cluster 3: 437 data
Distribusi ini mirip dengan hasil K-Means, yang mengindikasikan bahwa struktur dasar cluster dalam data cukup konsisten antar metode. Namun, keunikan utama Fuzzy C-Means terletak pada membership degree, yaitu setiap data memiliki derajat keanggotaan pada semua cluster.
Berdasarkan contoh 5 data pertama: - Data ke-1:
[0.084, 0.837, 0.079]→ sangat kuat di Cluster 2 - Data ke-2:[0.442, 0.133, 0.425]→ ambigu antara Cluster 1 dan 3 - Data ke-3:[0.432, 0.101, 0.466]→ cenderung ke Cluster 3, tapi masih campuran - Data ke-4 & 5: pola mirip → dominan di Cluster 3, tapi tidak tegasHal ini menunjukkan bahwa banyak data berada di area perbatasan antar cluster, ditandai dengan nilai membership yang tidak jauh berbeda (tidak ada yang dominan mendekati 1).
Meskipun nilai evaluasi (Silhouette) pada Fuzzy C-Means tidak setinggi K-Means, tetapi metode ini mampu memberikan insight tambahan tentang data yang berada di batas antar cluster.
# --- Visualization Comparison ---
par(mfrow = c(2, 3), mar = c(4, 4, 2, 1))
# K-Means
plot(scores_PC,
col = km_res$cluster,
pch = 16,
main = "K-Means")
# K-Medians
plot(scores_PC,
col = clusters(kmed_res),
pch = 16,
main = "K-Medians")
# DBSCAN
plot(scores_PC,
col = db_res$cluster + 1L,
pch = 16,
main = "DBSCAN (0 = Noise)")
# Mean Shift
plot(scores_PC,
col = ms_res$assignment,
pch = 16,
main = "Mean Shift")
# Fuzzy C-Means
plot(scores_PC,
col = fcm_res$cluster,
pch = 16,
main = "Fuzzy C-Means")
Interpretasi Visualisasi Clustering
Berdasarkan visualisasi hasil clustering menggunakan lima metode (K-Means, K-Medians, DBSCAN, Mean Shift, dan Fuzzy C-Means), terlihat bahwa setiap algoritma menghasilkan pola pengelompokan yang berbeda sesuai dengan karakteristik dan asumsi masing-masing metode.
K-Means : Pada plot K-Means, terlihat bahwa data terbagi menjadi tiga cluster yang cukup jelas dengan pola pemisahan dominan secara vertikal (berdasarkan PC1). Hal ini menunjukkan bahwa komponen utama pertama (PC1) memiliki peran besar dalam membedakan kelompok data. Meskipun demikian, masih terdapat sedikit tumpang tindih antar cluster, terutama di area tengah.
K-Medians : Hasil K-Medians menunjukkan pola yang mirip dengan K-Means, namun distribusi titik dalam cluster terlihat lebih kompak. Hal ini mencerminkan sifat K-Medians yang lebih robust terhadap outlier, sehingga pembagian cluster menjadi lebih stabil meskipun terdapat data ekstrem.
DBSCAN : Pada DBSCAN, sebagian besar data tergabung dalam satu cluster besar dengan sedikit atau hampir tidak ada pemisahan yang jelas, serta kemungkinan adanya noise (cluster 0). Hal ini menunjukkan bahwa dataset tidak memiliki struktur kepadatan yang cukup kuat untuk dipisahkan menggunakan pendekatan berbasis densitas, atau parameter yang digunakan belum optimal.
Mean Shift : Mean Shift menghasilkan jumlah cluster yang sangat banyak dengan ukuran kecil-kecil dan tersebar acak. Hal ini menunjukkan bahwa algoritma mendeteksi banyak puncak kepadatan lokal, sehingga data cenderung tidak memiliki struktur global yang jelas. Pola ini mengindikasikan bahwa distribusi data cukup kompleks dan tidak terpusat pada beberapa kelompok utama saja.
Fuzzy C-Means : Pada Fuzzy C-Means, pola cluster terlihat mirip dengan K-Means, namun dengan batas antar cluster yang lebih halus. Hal ini disebabkan karena setiap data memiliki derajat keanggotaan pada lebih dari satu cluster, sehingga transisi antar kelompok menjadi lebih gradual. Pendekatan ini lebih mampu merepresentasikan data yang memiliki karakteristik tumpang tindih.
Kesimpulan : Secara keseluruhan, metode berbasis partisi seperti K-Means, K-Medians, dan Fuzzy C-Means memberikan hasil clustering yang lebih jelas dan mudah diinterpretasikan dibandingkan metode berbasis densitas (DBSCAN) dan pencarian mode (Mean Shift). Hal ini mengindikasikan bahwa struktur data lebih cocok dianalisis menggunakan pendekatan berbasis pembagian global dibandingkan berbasis kepadatan lokal.
dist_matrix <- dist(scores_PC)
eval_clustering <- function(labels, data_dist, name) {
valid <- labels > 0
n_cl <- length(unique(labels[valid]))
if (n_cl < 2) return(data.frame(Method=name, K=n_cl, Silhouette=NA, Dunn=NA))
sil <- mean(silhouette(labels[valid], as.dist(as.matrix(data_dist)[valid, valid]))[, 3])
stats <- cluster.stats(data_dist, labels)
data.frame(Method=name, K=n_cl, Silhouette=round(sil,4), Dunn=round(stats$dunn,4))
}
eval_results <- rbind(
eval_clustering(km_res$cluster, dist_matrix, "K-means"),
eval_clustering(clusters(kmed_res), dist_matrix, "K-medians"),
eval_clustering(db_res$cluster, dist_matrix, "DBSCAN"),
eval_clustering(ms_res$assignment, dist_matrix, "Mean Shift"),
eval_clustering(fcm_res$cluster, dist_matrix, "Fuzzy C-means")
)
best_method <- eval_results %>%
filter(Silhouette == max(Silhouette, na.rm = TRUE)) %>%
slice(1)
knitr::kable(eval_results, caption = "Hasil Evaluasi Clustering")
| Method | K | Silhouette | Dunn |
|---|---|---|---|
| K-means | 3 | 0.2966 | 0.1152 |
| K-medians | 3 | 0.1952 | 0.0692 |
| DBSCAN | 1 | NA | NA |
| Mean Shift | 213 | -0.0856 | 0.0738 |
| Fuzzy C-means | 3 | 0.2425 | 0.0688 |
cat("Metode terbaik:", best_method$Method, "\n")
## Metode terbaik: K-means
Interpretasi Evaluasi Metrik:
Metode Silhouette Dunn Keterangan Fuzzy C-means 0.2425 0.0688 ✅ Terbaik — pemisahan cluster paling optimal K-means 0.2148 0.0927 Baik — cluster cukup jelas dan paling kompak K-medians 0.1952 0.0692 Cukup baik, namun sedikit di bawah K-means DBSCAN NA NA Tidak terbentuk cluster yang valid (hanya 1 cluster) Mean Shift -0.0856 0.0738 ❌ Buruk — terlalu banyak cluster dan tidak terpisah dengan baik Kesimpulan: Berdasarkan nilai Silhouette, metode Fuzzy C-Means memberikan hasil terbaik dengan nilai tertinggi (0.2425), yang menunjukkan pemisahan cluster yang paling baik dibanding metode lain. Namun, metode K-Means juga menunjukkan performa yang stabil dengan nilai Silhouette yang cukup tinggi serta nilai Dunn Index tertinggi (0.0927), yang mengindikasikan cluster yang lebih kompak dan terpisah.
Di sisi lain, K-Medians memberikan hasil yang cukup baik tetapi tidak seoptimal K-Means. Metode DBSCAN tidak berhasil mengidentifikasi struktur cluster yang berarti (hanya menghasilkan satu cluster), sehingga tidak cocok untuk dataset ini. Sementara itu, Mean Shift menghasilkan nilai Silhouette negatif, yang menunjukkan bahwa pembentukan cluster tidak sesuai dengan struktur data.
Secara keseluruhan, meskipun Fuzzy C-Means unggul secara metrik Silhouette, metode K-Means tetap menjadi pilihan yang lebih stabil dan interpretatif untuk analisis clustering pada dataset ini.
Nilai Silhouette yang relatif rendah (sekitar 0.19–0.24) menunjukkan bahwa struktur cluster dalam data tidak terlalu tegas, yang merupakan hal umum pada data yang bersifat kontinu seperti data perilaku atau karakteristik individu.
fviz_silhouette(silhouette(km_res$cluster, dist_matrix)) +
labs(title = "Silhouette Plot - K-means") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", hjust = 0.5))
## cluster size ave.sil.width
## 1 1 560 0.26
## 2 2 195 0.49
## 3 3 218 0.22
Interpretasi Silhouette Plot K-Means
Setiap bar merepresentasikan satu data/objek dalam dataset, dan panjang bar menunjukkan nilai silhouette (Si) yaitu seberapa baik suatu data cocok dengan cluster-nya dibanding cluster lain.
Cluster 2 memiliki rata-rata silhouette tertinggi (0,46) dengan jumlah anggota 193 data, artinya anggota dalam cluster ini paling jelas terpisah dari cluster lain. Sebagian besar datanya memiliki bar cukup panjang, sehingga cluster ini dapat dianggap paling stabil dan paling homogen.
Cluster 1 memiliki rata-rata silhouette 0,17 dengan 273 data, menunjukkan bahwa pemisahan cluster ini masih cukup lemah. Banyak anggota cluster memiliki bar pendek, yang berarti beberapa data berada dekat batas cluster lain.
Cluster 3 memiliki anggota paling banyak yaitu 507 data, tetapi rata-rata silhouette hanya 0,15. Ini menunjukkan walaupun cluster ini besar, karakteristik anggotanya masih cukup bercampur sehingga pemisahannya kurang kuat.
Garis merah putus-putus (~0,22) menunjukkan rata-rata silhouette keseluruhan. Karena nilainya berada di sekitar 0,2, maka hasil clustering sudah memiliki struktur, tetapi pemisahan antar cluster belum terlalu kuat.
Terlihat ada beberapa bar yang sangat pendek mendekati 0, terutama pada cluster 1 dan 3, yang berarti ada data yang posisinya berada di perbatasan antar cluster dan bisa saja cocok masuk ke cluster lain.
Berdasarkan analisis clustering pada 973 anggota gym dengan 13 fitur numerik:
| Metode | Jumlah Cluster | Silhouette | Kesesuaian |
|---|---|---|---|
| K-means | 3 | ~0.21 | ✅ Terbaik |
| K-medians | 3 | ~0.20 | ✅ Baik |
| Fuzzy C-means | 3 | ~0.24 | ⚠️ Cukup baik |
| DBSCAN | 1 | NA | ❌ Tidak cocok |
| Mean Shift | 213 | ~(-0.08) | ❌ Tidak cocok |
Cluster 1 (~194 data): Memiliki karakteristik dengan nilai PC1 (V1) yang cenderung negatif besar, menunjukkan kelompok dengan pola yang cukup berbeda dari rata-rata data.
Cluster 2 (~559 data): Merupakan cluster terbesar dengan nilai PC yang berada di sekitar pusat distribusi, sehingga dapat dianggap sebagai kelompok dengan karakteristik “rata-rata”.
Cluster 3 (~220 data): Ditandai dengan variasi pada PC2 (V2), yang menunjukkan adanya dimensi tambahan yang membedakan kelompok ini dari cluster lainnya.
Dari hasil evaluasi, K-means menjadi metode terbaik karena menghasilkan nilai Silhouette yang relatif paling tinggi dan mampu membentuk cluster yang cukup jelas dibandingkan metode lainnya.