Najwa Ayu Ramadhani - 24031554168
Alvida Lewiana - 24031554198
Dataset: Wine Dataset
Sumber: Kaggle → https://www.kaggle.com/datasets/harrywang/wine-dataset-for-clustering
Tentang data: Dataset ini berisi hasil uji kimia dari 178 sampel wine asal Italia yang berasal dari 3 jenis kultivar anggur yang berbeda. Terdapat 13 fitur numerik yang merepresentasikan berbagai karakteristik kimia, seperti kadar alkohol, kandungan fenol, hingga prolin.
Pada tahap awal, dilakukan pemanggilan package yang dibutuhkan untuk proses analisis. Apabila package belum terinstal, pengguna dapat mengaktifkan perintah install.packages() untuk melakukan instalasi terlebih dahulu.
Tahap ini bertujuan untuk memastikan seluruh fungsi yang diperlukan dalam analisis dapat digunakan dengan baik.
install.packages(c("tidyverse", "corrplot", "flexclust", "dbscan",
"e1071", "cluster", "fpc", "factoextra", "mclust"))
library(tidyverse) # manipulasi & visualisasi data
library(corrplot) # heatmap korelasi
library(flexclust) # K-Medians via kcca()
library(dbscan) # DBSCAN
library(e1071) # Fuzzy C-Means via cmeans()
library(cluster) # silhouette()
library(fpc) # cluster.stats() untuk Dunn Index
library(factoextra) # fviz_nbclust, fviz_silhouette, fviz_cluster
Installing packages into ‘/usr/local/lib/R/site-library’
(as ‘lib’ is unspecified)
also installing the dependencies ‘colorspace’, ‘fracdiff’, ‘lmtest’, ‘timeDate’, ‘urca’, ‘zoo’, ‘RcppArmadillo’, ‘Deriv’, ‘forecast’, ‘microbenchmark’, ‘rbibutils’, ‘doBy’, ‘SparseM’, ‘MatrixModels’, ‘Rdpack’, ‘minqa’, ‘nloptr’, ‘reformulas’, ‘RcppEigen’, ‘lazyeval’, ‘carData’, ‘abind’, ‘Formula’, ‘pbkrtest’, ‘quantreg’, ‘lme4’, ‘crosstalk’, ‘estimability’, ‘mvtnorm’, ‘numDeriv’, ‘DEoptimR’, ‘viridis’, ‘car’, ‘DT’, ‘ellipse’, ‘emmeans’, ‘flashClust’, ‘leaps’, ‘multcompView’, ‘scatterplot3d’, ‘ggsci’, ‘cowplot’, ‘ggsignif’, ‘gridExtra’, ‘polynom’, ‘rstatix’, ‘modeltools’, ‘proxy’, ‘flexmix’, ‘prabclus’, ‘diptest’, ‘robustbase’, ‘kernlab’, ‘dendextend’, ‘FactoMineR’, ‘ggpubr’, ‘ggrepel’
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.2.0 ✔ readr 2.2.0
✔ forcats 1.0.1 ✔ stringr 1.6.0
✔ ggplot2 4.0.2 ✔ tibble 3.3.1
✔ lubridate 1.9.5 ✔ tidyr 1.3.2
✔ purrr 1.2.1
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
corrplot 0.95 loaded
Attaching package: ‘dbscan’
The following object is masked from ‘package:stats’:
as.dendrogram
Attaching package: ‘e1071’
The following object is masked from ‘package:flexclust’:
bclust
The following object is masked from ‘package:ggplot2’:
element
Attaching package: ‘fpc’
The following object is masked from ‘package:dbscan’:
dbscan
Welcome to factoextra!
Want to learn more? See two factoextra-related books at https://www.datanovia.com/en/product/practical-guide-to-principal-component-methods-in-r/
Dataset Wine dimuat dari file berformat CSV, kemudian dilakukan pemeriksaan awal untuk memahami struktur dan isi data. Tahap ini mencakup pengecekan jumlah observasi, jumlah variabel, serta tipe data pada setiap fitur.
Selain itu, dilakukan peninjauan singkat terhadap beberapa baris awal data untuk memastikan bahwa data telah terbaca dengan benar dan siap digunakan pada tahap analisis selanjutnya.
df_wine <- read.csv("wine-clustering.csv", sep = ",")
cat("Dimensi dataset:", nrow(df_wine), "baris x", ncol(df_wine), "kolom\n\n")
head(df_wine)
Dimensi dataset: 178 baris x 13 kolom
A data.frame: 6 × 13
| Alcohol <dbl> | Malic_Acid <dbl> | Ash <dbl> | Ash_Alcanity <dbl> | Magnesium <int> | Total_Phenols <dbl> | Flavanoids <dbl> | Nonflavanoid_Phenols <dbl> | Proanthocyanins <dbl> | Color_Intensity <dbl> | Hue <dbl> | OD280 <dbl> | Proline <int> | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 14.23 | 1.71 | 2.43 | 15.6 | 127 | 2.80 | 3.06 | 0.28 | 2.29 | 5.64 | 1.04 | 3.92 | 1065 |
| 2 | 13.20 | 1.78 | 2.14 | 11.2 | 100 | 2.65 | 2.76 | 0.26 | 1.28 | 4.38 | 1.05 | 3.40 | 1050 |
| 3 | 13.16 | 2.36 | 2.67 | 18.6 | 101 | 2.80 | 3.24 | 0.30 | 2.81 | 5.68 | 1.03 | 3.17 | 1185 |
| 4 | 14.37 | 1.95 | 2.50 | 16.8 | 113 | 3.85 | 3.49 | 0.24 | 2.18 | 7.80 | 0.86 | 3.45 | 1480 |
| 5 | 13.24 | 2.59 | 2.87 | 21.0 | 118 | 2.80 | 2.69 | 0.39 | 1.82 | 4.32 | 1.04 | 2.93 | 735 |
| 6 | 14.20 | 1.76 | 2.45 | 15.2 | 112 | 3.27 | 3.39 | 0.34 | 1.97 | 6.75 | 1.05 | 2.85 | 1450 |
str(df_wine)
'data.frame': 178 obs. of 13 variables:
$ Alcohol : num 14.2 13.2 13.2 14.4 13.2 ...
$ Malic_Acid : num 1.71 1.78 2.36 1.95 2.59 1.76 1.87 2.15 1.64 1.35 ...
$ Ash : num 2.43 2.14 2.67 2.5 2.87 2.45 2.45 2.61 2.17 2.27 ...
$ Ash_Alcanity : num 15.6 11.2 18.6 16.8 21 15.2 14.6 17.6 14 16 ...
$ Magnesium : int 127 100 101 113 118 112 96 121 97 98 ...
$ Total_Phenols : num 2.8 2.65 2.8 3.85 2.8 3.27 2.5 2.6 2.8 2.98 ...
$ Flavanoids : num 3.06 2.76 3.24 3.49 2.69 3.39 2.52 2.51 2.98 3.15 ...
$ Nonflavanoid_Phenols: num 0.28 0.26 0.3 0.24 0.39 0.34 0.3 0.31 0.29 0.22 ...
$ Proanthocyanins : num 2.29 1.28 2.81 2.18 1.82 1.97 1.98 1.25 1.98 1.85 ...
$ Color_Intensity : num 5.64 4.38 5.68 7.8 4.32 6.75 5.25 5.05 5.2 7.22 ...
$ Hue : num 1.04 1.05 1.03 0.86 1.04 1.05 1.02 1.06 1.08 1.01 ...
$ OD280 : num 3.92 3.4 3.17 3.45 2.93 2.85 3.58 3.58 2.85 3.55 ...
$ Proline : int 1065 1050 1185 1480 735 1450 1290 1295 1045 1045 ...
summary(df_wine)
Alcohol Malic_Acid Ash Ash_Alcanity
Min. :11.03 Min. :0.740 Min. :1.360 Min. :10.60
1st Qu.:12.36 1st Qu.:1.603 1st Qu.:2.210 1st Qu.:17.20
Median :13.05 Median :1.865 Median :2.360 Median :19.50
Mean :13.00 Mean :2.336 Mean :2.367 Mean :19.49
3rd Qu.:13.68 3rd Qu.:3.083 3rd Qu.:2.558 3rd Qu.:21.50
Max. :14.83 Max. :5.800 Max. :3.230 Max. :30.00
Magnesium Total_Phenols Flavanoids Nonflavanoid_Phenols
Min. : 70.00 Min. :0.980 Min. :0.340 Min. :0.1300
1st Qu.: 88.00 1st Qu.:1.742 1st Qu.:1.205 1st Qu.:0.2700
Median : 98.00 Median :2.355 Median :2.135 Median :0.3400
Mean : 99.74 Mean :2.295 Mean :2.029 Mean :0.3619
3rd Qu.:107.00 3rd Qu.:2.800 3rd Qu.:2.875 3rd Qu.:0.4375
Max. :162.00 Max. :3.880 Max. :5.080 Max. :0.6600
Proanthocyanins Color_Intensity Hue OD280
Min. :0.410 Min. : 1.280 Min. :0.4800 Min. :1.270
1st Qu.:1.250 1st Qu.: 3.220 1st Qu.:0.7825 1st Qu.:1.938
Median :1.555 Median : 4.690 Median :0.9650 Median :2.780
Mean :1.591 Mean : 5.058 Mean :0.9574 Mean :2.612
3rd Qu.:1.950 3rd Qu.: 6.200 3rd Qu.:1.1200 3rd Qu.:3.170
Max. :3.580 Max. :13.000 Max. :1.7100 Max. :4.000
Proline
Min. : 278.0
1st Qu.: 500.5
Median : 673.5
Mean : 746.9
3rd Qu.: 985.0
Max. :1680.0
Sebelum melakukan proses clustering, dilakukan tahap Exploratory Data Analysis (EDA) untuk memahami karakteristik data secara umum. Tahap ini mencakup pemeriksaan distribusi masing-masing fitur, identifikasi keberadaan outlier, serta analisis hubungan antar variabel.
EDA bertujuan untuk memberikan gambaran awal mengenai pola data, sehingga dapat membantu dalam menentukan metode analisis yang tepat pada tahap selanjutnya.
Pemeriksaan missing value dilakukan untuk memastikan tidak terdapat nilai yang hilang dalam dataset. Keberadaan nilai kosong dapat memengaruhi proses analisis, termasuk menyebabkan error atau menghasilkan hasil clustering yang kurang optimal.
Oleh karena itu, tahap ini bertujuan untuk memastikan bahwa data dalam kondisi lengkap dan siap digunakan pada tahap selanjutnya.
cat("Jumlah missing value per kolom:\n")
print(colSums(is.na(df_wine)))
cat("\nTotal missing value:", sum(is.na(df_wine)), "\n")
Jumlah missing value per kolom:
Alcohol Malic_Acid Ash
0 0 0
Ash_Alcanity Magnesium Total_Phenols
0 0 0
Flavanoids Nonflavanoid_Phenols Proanthocyanins
0 0 0
Color_Intensity Hue OD280
0 0 0
Proline
0
Total missing value: 0
cat("Jumlah baris duplikat:", sum(duplicated(df_wine)), "\n")
Jumlah baris duplikat: 0
Boxplot digunakan untuk mengidentifikasi sebaran data serta mendeteksi keberadaan outlier pada setiap variabel. Melalui boxplot, dapat diamati median, kuartil, serta rentang data secara ringkas.
Histogram digunakan untuk melihat bentuk distribusi data pada masing-masing variabel, seperti apakah data berdistribusi normal, skewed (miring), atau memiliki lebih dari satu puncak (bimodal). Analisis ini membantu memahami karakteristik dasar data sebelum dilakukan proses clustering.
wine_long <- df_wine %>%
pivot_longer(cols = everything(), names_to = "Fitur", values_to = "Nilai")
# Boxplot semua fitur — pakai palet warna custom yang lebih menarik
ggplot(wine_long, aes(x = Fitur, y = Nilai, fill = Fitur)) +
geom_boxplot(show.legend = FALSE,
outlier.color = "#FF6B6B",
outlier.alpha = 0.7,
outlier.shape = 18,
outlier.size = 2.5) +
coord_flip() +
scale_fill_manual(values = colorRampPalette(
c("#6C5CE7", "#A29BFE", "#74B9FF", "#00CEC9",
"#55EFC4", "#FDCB6E", "#E17055", "#D63031"))(13)) +
theme_minimal(base_size = 11) +
theme(panel.grid.minor = element_blank()) +
labs(
title = "Distribusi 13 Fitur Kimia Wine (Data Mentah)",
subtitle = "Titik merah-oranye = potensi outlier di luar 1.5×IQR",
x = NULL, y = "Nilai"
)
Titik-titik merah di luar garis "kumis" (whisker) menunjukkan adanya pencilan (outlier). Fitur seperti Malic_Acid, Ash, dan Color_Intensity memiliki beberapa sampel dengan nilai ekstrem yang dapat memengaruhi hasil clustering jika tidak ditangani. erbedaan skala yang ekstrem ini menjadi alasan kuat dilakukannya standarisasi Z-Score pada tahap berikutnya agar semua fitur memiliki bobot yang sama dalam perhitungan jarak.
# Histogram tiap fitur
ggplot(wine_long, aes(x = Nilai, fill = Fitur)) +
geom_histogram(bins = 22, show.legend = FALSE,
color = "white", alpha = 0.88) +
facet_wrap(~Fitur, scales = "free") +
scale_fill_manual(values = colorRampPalette(
c("#6C5CE7", "#00CEC9", "#FDCB6E", "#E17055", "#55EFC4"))(13)) +
theme_minimal(base_size = 9) +
theme(strip.text = element_text(size = 7.5, face = "bold")) +
labs(
title = "Histogram 13 Fitur Kimia Wine",
subtitle = "Terlihat beberapa fitur berdistribusi tidak normal (skewed / bimodal)",
x = "Nilai", y = "Frekuensi"
)
Sebagian besar fitur tidak berdistribusi normal sempurna. Beberapa fitur menunjukkan kemiringan (skewness). Fitur seperti Flavanoids, Total_Phenols, dan OD280 menunjukkan distribusi bimodal (memiliki dua puncak). Hal ini merupakan indikasi awal secara visual bahwa dataset ini memang terdiri dari kelompok-kelompok yang berbeda secara kimiawi.
Heatmap korelasi digunakan untuk mengidentifikasi hubungan linear antar fitur dalam dataset. Analisis ini membantu mengetahui sejauh mana keterkaitan antar variabel.
Fitur yang memiliki korelasi tinggi cenderung membawa informasi yang serupa. Hal ini perlu diperhatikan karena dapat memengaruhi hasil clustering, terutama pada metode berbasis jarak, di mana fitur yang saling berkorelasi kuat dapat memberikan pengaruh yang berlebihan dalam pembentukan klaster.
corrplot(mat_kor,
method = "color",
type = "full",
addCoef.col = "black",
tl.col = "black",
tl.srt = 45,
number.cex = 0.52,
col = colorRampPalette(c("#6C5CE7", "white", "#E17055"))(200),
title = "Heatmap Korelasi — 13 Fitur Kimia Wine",
mar = c(0, 0, 1.5, 0))
Temuan menarik dari korelasi:
Total_Phenols & Flavanoids:
menunjukkan korelasi positif yang kuat (≈ 0,86), yang mengindikasikan
bahwa wine dengan kandungan fenol tinggi cenderung juga memiliki
kandungan flavanoid yang tinggi.OD280 & Flavanoids: juga memiliki
korelasi positif yang kuat (≈ 0,79), yang menunjukkan adanya keterkaitan
antara nilai OD280 dengan kandungan flavanoid dalam wine.Malic_Acid & Hue: menunjukkan korelasi
negatif, yang mengindikasikan bahwa semakin tinggi tingkat keasaman,
maka warna wine cenderung lebih gelap.Sebelum clustering, ada dua hal yang wajib dilakukan:
# Drop missing value dan duplikat (kalau ada)
df_clean <- df_wine %>%
drop_na() %>%
distinct()
cat("Baris setelah pembersihan:", nrow(df_clean), "\n")
Baris setelah pembersihan: 178
# Standarisasi Z-Score: mean=0, sd=1 untuk semua fitur
# Cek dulu kalau ada kolom yang variansinya nol (konstan) — kalau ada, hapus
kolom_nol_var <- names(df_clean)[sapply(df_clean, var) == 0]
if (length(kolom_nol_var) > 0) {
cat("Kolom variansi nol ditemukan dan dihapus:", paste(kolom_nol_var, collapse=", "), "\n")
df_clean <- df_clean %>% select(-all_of(kolom_nol_var))
} else {
cat("Tidak ada kolom bervariansi nol — semua fitur aman dipakai.\n")
}
df_scaled <- as.data.frame(scale(df_clean))
cat("Cek NaN hasil scaling:", sum(is.nan(as.matrix(df_scaled))), "\n")
cat("Cek Inf hasil scaling:", sum(is.infinite(as.matrix(df_scaled))), "\n")
cat("\nData siap! Dimensi akhir:", nrow(df_scaled), "x", ncol(df_scaled), "\n")
Tidak ada kolom bervariansi nol — semua fitur aman dipakai.
Cek NaN hasil scaling: 0
Cek Inf hasil scaling: 0
Data siap! Dimensi akhir: 178 x 13
# Verifikasi: boxplot setelah scaling — semua fitur harusnya pada skala Z-Score
wine_scaled_long <- df_scaled %>%
pivot_longer(cols = everything(), names_to = "Fitur", values_to = "Z_Score")
ggplot(wine_scaled_long, aes(x = Fitur, y = Z_Score, fill = Fitur)) +
geom_boxplot(show.legend = FALSE,
outlier.color = "#FF6B6B",
outlier.alpha = 0.6) +
coord_flip() +
scale_fill_manual(values = colorRampPalette(
c("#6C5CE7", "#A29BFE", "#74B9FF", "#00CEC9",
"#55EFC4", "#FDCB6E", "#E17055", "#D63031"))(13)) +
theme_minimal(base_size = 11) +
labs(
title = "Boxplot Setelah Standarisasi Z-Score",
subtitle = "Semua fitur kini pada skala yang sebanding",
x = NULL, y = "Z-Score"
)
di sini semua fitur berada pada rentang yang sebanding (berpusat di 0 dengan standar deviasi 1). Grafik ini mengonfirmasi bahwa proses preprocessing berhasil, sehingga algoritma seperti K-Means dapat menghitung jarak antar titik secara adil tanpa didominasi oleh fitur dengan angka nominal besar.
Penentuan jumlah klaster optimal dilakukan menggunakan dua pendekatan, yaitu Elbow Method dan Silhouette Method.
set.seed(42)
fviz_nbclust(df_scaled, kmeans, method = "wss", k.max = 10) +
geom_vline(xintercept = 3, linetype = "dashed",
color = "#E17055", linewidth = 1) +
annotate("text", x = 3.2, y = max(
sapply(1:10, function(k) kmeans(df_scaled, k, nstart=10)$tot.withinss)
) * 0.95,
label = "k = 3", color = "#E17055",
fontface = "bold", hjust = 0) +
scale_color_manual(values = "#6C5CE7") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold")) +
labs(
title = "Elbow Method — Mencari Titik Siku",
subtitle = "Penurunan WSS mulai melambat di k = 3"
)
Grafik menunjukkan penurunan Within-Cluster Sum of Squares (WSS) seiring bertambahnya jumlah klaster (k). Terlihat tekukan atau "siku" yang paling jelas pada k = 3. Setelah k=3, penurunan WSS menjadi lebih landai, menandakan bahwa menambah klaster lebih dari 3 tidak lagi memberikan peningkatan signifikan dalam kepadatan klaster.
set.seed(42)
fviz_nbclust(df_scaled, kmeans, method = "silhouette", k.max = 10) +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold")) +
labs(
title = "Silhouette Method — Nilai Tertinggi = k Terbaik",
subtitle = "Silhouette rata-rata tertinggi berada di k = 3"
)
optimal_k <- 3
cat("k optimal yang dipilih:", optimal_k, "\n")
cat("Alasan: Elbow & Silhouette sama-sama menunjuk k=3,")
cat(" dan dataset wine memang berasal dari 3 kultivar.\n")
k optimal yang dipilih: 3
Alasan: Elbow & Silhouette sama-sama menunjuk k=3, dan dataset wine memang berasal dari 3 kultivar.
Nilai tertinggi berada pada k = 3. Ini memberikan validasi objektif kedua (setelah Elbow Method) bahwa membagi data menjadi 3 kelompok adalah pilihan yang paling optimal.
K-Means merupakan algoritma clustering berbasis partisi yang bekerja dengan meminimalkan total jarak kuadrat antara setiap titik data dengan pusat klaster (centroid). Metode ini dikenal sederhana, cepat, dan cukup efektif, terutama untuk data yang memiliki bentuk klaster menyerupai bola atau elips.
Salah satu parameter penting dalam K-Means adalah nstart, yaitu
jumlah inisialisasi awal yang digunakan. Nilai nstart = 25
menunjukkan bahwa algoritma dijalankan dengan 25 titik awal yang
berbeda, kemudian dipilih hasil terbaik. Pendekatan ini bertujuan untuk
mengurangi kemungkinan algoritma terjebak pada solusi lokal (local
optimum).
set.seed(42)
km_res <- kmeans(df_scaled, centers = optimal_k, nstart = 25)
cat(" Hasil K-Means (k=3) \n")
cat("Ukuran tiap klaster:\n")
print(table(km_res$cluster))
cat("\nTotal Within-cluster SS :", round(km_res$tot.withinss, 2))
cat("\nBetween-cluster SS :", round(km_res$betweenss, 2))
cat("\nRasio BSS/TSS :", round(km_res$betweenss / km_res$totss * 100, 2), "%\n")
cat("\n(BSS/TSS makin tinggi = pemisahan klaster makin baik)\n")
Hasil K-Means (k=3)
Ukuran tiap klaster:
1 2 3
62 65 51
Total Within-cluster SS : 1270.75
Between-cluster SS : 1030.25
Rasio BSS/TSS : 44.77 %
(BSS/TSS makin tinggi = pemisahan klaster makin baik)
# Heatmap profiling: nilai rata-rata tiap fitur per klaster (data asli)
profil_km <- df_clean %>%
mutate(klaster = as.factor(km_res$cluster)) %>%
group_by(klaster) %>%
summarise(across(where(is.numeric), ~round(mean(.x, na.rm=TRUE), 2)))
print(profil_km)
# A tibble: 3 × 14
klaster Alcohol Malic_Acid Ash Ash_Alcanity Magnesium Total_Phenols
<fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1 13.7 2 2.47 17.5 108. 2.85
2 2 12.2 1.9 2.23 20.1 92.7 2.25
3 3 13.1 3.31 2.42 21.2 98.7 1.68
# ℹ 7 more variables: Flavanoids <dbl>, Nonflavanoid_Phenols <dbl>,
# Proanthocyanins <dbl>, Color_Intensity <dbl>, Hue <dbl>, OD280 <dbl>,
# Proline <dbl>
Hierarchical Clustering merupakan metode clustering yang tidak memerlukan penentuan jumlah klaster (k) di awal. Metode ini bekerja dengan membangun struktur hierarki dalam bentuk dendrogram, yang menggambarkan proses penggabungan observasi secara bertahap berdasarkan tingkat kemiripannya.
Pada analisis ini digunakan metode Ward, yaitu pendekatan yang bertujuan untuk meminimalkan variasi (variance) dalam setiap klaster. Metode ini cenderung menghasilkan klaster yang lebih kompak dan seimbang dibandingkan metode lain seperti complete linkage atau average linkage.
Kita pakai metode Ward karena ia meminimalkan variance total dalam klaster — hasilnya cenderung lebih kompak dan seimbang dibanding metode lain (complete, average, dll).
# Hitung jarak antar observasi (Euclidean) dari data yang sudah di-scale
mat_jarak <- dist(df_scaled, method = "euclidean")
# Hierarchical clustering dengan metode Ward.D2
hc_res <- hclust(mat_jarak, method = "ward.D2")
# Tampilkan dendrogram
par(mar = c(4, 4, 3, 1))
plot(hc_res,
main = "Dendrogram — Hierarchical Clustering (Ward.D2)",
xlab = "Sampel Wine",
ylab = "Jarak (Height)",
labels = FALSE,
hang = -1,
cex.main = 1.1)
# Tambahkan garis potong di k=3
abline(h = 9.5, col = "#E17055", lty = 2, lwd = 2)
text(x = nrow(df_scaled) * 0.05, y = 10.2,
labels = "Potong di k=3",
col = "#E17055", cex = 0.9, font = 2)
# Potong dendrogram menjadi 3 klaster
hc_cluster <- cutree(hc_res, k = optimal_k)
cat("Distribusi klaster Hierarchical Clustering:\n")
print(table(hc_cluster))
Distribusi klaster Hierarchical Clustering:
hc_cluster
1 2 3
64 58 56
# Visualisasi dendrogram dengan warna per klaster
library(factoextra)
fviz_dend(hc_res,
k = optimal_k,
rect = TRUE,
rect_fill = TRUE,
k_colors = c("#6C5CE7", "#E17055", "#00CEC9"),
rect_border = c("#6C5CE7", "#E17055", "#00CEC9"),
labels_track_height = 0.8,
show_labels = FALSE,
main = "Dendrogram Hierarchical Clustering (Ward.D2, k=3)",
xlab = "Sampel Wine",
ylab = "Height") +
theme_minimal()
Grafik ini menunjukkan bagaimana setiap sampel wine digabungkan satu sama lain berdasarkan kemiripan. Menarik garis horizontal pada ketinggian tertentu yang membagi pohon menjadi 3 cabang utama. Warna yang berbeda (Ungu, Oranye, Hijau) pada halaman 19 menunjukkan keanggotaan sampel dalam 3 klaster tersebut
# Profiling klaster Hierarchical
profil_hc <- df_clean %>%
mutate(klaster = as.factor(hc_cluster)) %>%
group_by(klaster) %>%
summarise(across(where(is.numeric), ~round(mean(.x, na.rm=TRUE), 2)))
print(profil_hc)
# A tibble: 3 × 14
klaster Alcohol Malic_Acid Ash Ash_Alcanity Magnesium Total_Phenols
<fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1 13.7 1.97 2.46 17.5 106. 2.85
2 2 12.2 1.94 2.22 20.2 92.6 2.26
3 3 13.1 3.17 2.41 21 99.9 1.69
# ℹ 7 more variables: Flavanoids <dbl>, Nonflavanoid_Phenols <dbl>,
# Proanthocyanins <dbl>, Color_Intensity <dbl>, Hue <dbl>, OD280 <dbl>,
# Proline <dbl>
K-Medians merupakan metode clustering yang serupa dengan K-Means, namun menggunakan median sebagai pusat klaster, bukan mean. Penggunaan median membuat metode ini lebih robust terhadap outlier, karena tidak terlalu dipengaruhi oleh nilai ekstrem dalam data.
Selain itu, K-Medians menggunakan Manhattan distance (L1) sebagai ukuran jarak antar data, berbeda dengan K-Means yang menggunakan Euclidean distance (L2). Perbedaan ini memengaruhi cara pembentukan klaster, terutama pada data yang memiliki distribusi tidak simetris atau mengandung outlier.
set.seed(42)
kmed_res <- kcca(df_scaled, k = optimal_k,
family = kccaFamily("kmedians"))
cat("Distribusi klaster K-Medians:\n")
print(table(clusters(kmed_res)))
Distribusi klaster K-Medians:
1 2 3
63 65 50
# Profiling K-Medians
profil_kmed <- df_clean %>%
mutate(klaster = as.factor(clusters(kmed_res))) %>%
group_by(klaster) %>%
summarise(across(where(is.numeric), ~round(mean(.x, na.rm=TRUE), 2)))
print(profil_kmed)
# A tibble: 3 × 14
klaster Alcohol Malic_Acid Ash Ash_Alcanity Magnesium Total_Phenols
<fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1 13.7 1.98 2.46 17.5 106. 2.86
2 2 12.2 1.9 2.23 20 94.2 2.23
3 3 13.1 3.35 2.43 21.3 98.6 1.68
# ℹ 7 more variables: Flavanoids <dbl>, Nonflavanoid_Phenols <dbl>,
# Proanthocyanins <dbl>, Color_Intensity <dbl>, Hue <dbl>, OD280 <dbl>,
# Proline <dbl>
DBSCAN (Density-Based Spatial Clustering of Applications with Noise) merupakan metode clustering yang memiliki pendekatan berbeda dibandingkan metode berbasis partisi. Metode ini mengelompokkan data berdasarkan kepadatan (density), yaitu dengan mengidentifikasi wilayah yang memiliki konsentrasi titik tinggi.
DBSCAN menggunakan dua parameter utama, yaitu:
Titik data yang tidak termasuk ke dalam klaster mana pun akan dianggap sebagai noise dan biasanya diberi label 0.
kNNdistplot(df_scaled, k = 4)
abline(h = 3.0, col = "#E17055", lty = 2, lwd = 2)
title(main = "kNN Distance Plot — Menentukan eps DBSCAN",
sub = "Garis oranye = eps = 3.0 (titik 'lutut')")
Grafik ini digunakan untuk mencari nilai eps (radius) untuk algoritma DBSCAN. Kami memilih eps = 3.0 berdasarkan titik di mana kurva mulai naik tajam. Namun, karena kurvanya cukup landai, ini menandakan bahwa data wine tidak memiliki perbedaan kepadatan yang ekstrem, yang menjelaskan mengapa DBSCAN kurang efektif pada dataset ini.
# Tuning: coba berbagai nilai eps
cat(" Hasil Tuning Parameter DBSCAN (MinPts = 5)\n")
cat(sprintf("%-8s | %-10s | %-10s\n", "eps", "Klaster", "Noise (%)"))
cat(strrep("-", 35), "\n")
for (e in c(1.5, 2.0, 2.5, 3.0, 3.5, 4.0)) {
tmp <- dbscan(df_scaled, eps = e, MinPts = 5)
n_kl <- length(unique(tmp$cluster[tmp$cluster != 0]))
n_noise <- sum(tmp$cluster == 0)
cat(sprintf("%-8.1f | %-10d | %.1f%%\n",
e, n_kl, n_noise / nrow(df_scaled) * 100))
}
cat("\n→ eps = 3.0 dipilih: 2 klaster, noise hanya 2.8%\n")
Hasil Tuning Parameter DBSCAN (MinPts = 5)
eps | Klaster | Noise (%)
-----------------------------------
1.5 | 0 | 100.0%
2.0 | 4 | 47.8%
2.5 | 1 | 12.9%
3.0 | 1 | 6.2%
3.5 | 1 | 3.4%
4.0 | 1 | 1.1%
→ eps = 3.0 dipilih: 2 klaster, noise hanya 2.8%
# Implementasi DBSCAN final
db_res <- dbscan(df_scaled, eps = 3.0, MinPts = 5)
cat("Distribusi klaster DBSCAN (0 = noise):\n")
print(table(db_res$cluster))
cat("Jumlah noise:", sum(db_res$cluster == 0),
"titik (", round(mean(db_res$cluster==0)*100, 1), "%)\n")
Distribusi klaster DBSCAN (0 = noise):
0 1
11 167
Jumlah noise: 11 titik ( 6.2 %)
Fuzzy C-Means (FCM) merupakan metode clustering yang bersifat soft clustering, berbeda dengan metode sebelumnya yang bersifat hard clustering (setiap data hanya masuk ke satu klaster). Pada FCM, setiap data memiliki derajat keanggotaan pada setiap klaster dengan nilai antara 0 hingga 1, dan total derajat keanggotaan untuk setiap data bernilai 1.
Pendekatan ini relevan untuk dataset seperti Wine, karena batas antar kultivar secara kimia tidak selalu jelas, sehingga suatu sampel dapat memiliki karakteristik yang mirip dengan lebih dari satu klaster.
Salah satu parameter penting dalam FCM adalah m (fuzziness parameter). Umumnya digunakan nilai m = 2, di mana semakin besar nilai m, maka batas antar klaster akan menjadi semakin tidak tegas (lebih kabur).
set.seed(42)
fcm_res <- cmeans(as.matrix(df_scaled), centers = optimal_k, m = 2)
cat("Distribusi klaster Fuzzy C-Means:\n")
print(table(fcm_res$cluster))
cat("\nContoh derajat keanggotaan (5 data pertama):\n")
print(round(head(fcm_res$membership, 5), 3))
Distribusi klaster Fuzzy C-Means:
1 2 3
62 65 51
Contoh derajat keanggotaan (5 data pertama):
1 2 3
[1,] 0.721 0.171 0.108
[2,] 0.567 0.292 0.141
[3,] 0.695 0.194 0.111
[4,] 0.673 0.184 0.143
[5,] 0.490 0.310 0.200
# Profiling FCM
profil_fcm <- df_clean %>%
mutate(klaster = as.factor(fcm_res$cluster)) %>%
group_by(klaster) %>%
summarise(across(where(is.numeric), ~round(mean(.x, na.rm=TRUE), 2)))
print(profil_fcm)
# A tibble: 3 × 14
klaster Alcohol Malic_Acid Ash Ash_Alcanity Magnesium Total_Phenols
<fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1 13.7 2 2.47 17.5 108. 2.85
2 2 12.2 1.9 2.23 20.1 92.7 2.25
3 3 13.1 3.31 2.42 21.2 98.7 1.68
# ℹ 7 more variables: Flavanoids <dbl>, Nonflavanoid_Phenols <dbl>,
# Proanthocyanins <dbl>, Color_Intensity <dbl>, Hue <dbl>, OD280 <dbl>,
# Proline <dbl>
Evaluasi clustering dilakukan menggunakan dua metrik evaluasi internal, yaitu metrik yang tidak memerlukan label kelas asli. Metrik ini digunakan untuk menilai kualitas hasil pengelompokan berdasarkan struktur data itu sendiri.
| Metrik | Deskripsi | Skor Ideal |
|---|---|---|
| Silhouette Score | Seberapa mirip tiap titik dengan klasternya sendiri vs klaster terdekat | Mendekati +1 |
| Dunn Index | Rasio jarak minimum antar-klaster ÷ diameter klaster maksimum | Semakin tinggi semakin baik |
Nilai Silhouette Score yang tinggi menunjukkan bahwa data terkelompok dengan baik dan memiliki pemisahan yang jelas antar klaster. Sementara itu, Dunn Index yang tinggi menunjukkan bahwa klaster memiliki jarak yang cukup jauh satu sama lain serta memiliki ukuran yang relatif kompak.
# ── Silhouette Score ──────────────────────────────────────────────
sil_km <- silhouette(km_res$cluster, dist(df_scaled))
sil_hc <- silhouette(hc_cluster, dist(df_scaled))
sil_kmed <- silhouette(clusters(kmed_res), dist(df_scaled))
sil_fcm <- silhouette(fcm_res$cluster, dist(df_scaled))
# DBSCAN: hitung hanya untuk non-noise
db_valid <- db_res$cluster[db_res$cluster != 0]
df_db_val <- df_scaled[db_res$cluster != 0, ]
if (length(unique(db_valid)) > 1) {
sil_db <- silhouette(db_valid, dist(df_db_val))
sil_db_score <- round(mean(sil_db[, 3]), 4)
} else {
sil_db_score <- NA
}
# ── Dunn Index ────────────────────────────────────────────────────
dunn_km <- round(cluster.stats(dist(df_scaled), km_res$cluster)$dunn, 4)
dunn_hc <- round(cluster.stats(dist(df_scaled), hc_cluster)$dunn, 4)
dunn_kmed <- round(cluster.stats(dist(df_scaled), clusters(kmed_res))$dunn, 4)
dunn_fcm <- round(cluster.stats(dist(df_scaled), fcm_res$cluster)$dunn, 4)
if (!is.na(sil_db_score)) {
dunn_db <- round(cluster.stats(dist(df_db_val), db_valid)$dunn, 4)
} else {
dunn_db <- NA
}
# ── Tabel Perbandingan ────────────────────────────────────────────
tabel_eval <- data.frame(
Metode = c("K-Means", "Hierarchical", "K-Medians", "DBSCAN", "Fuzzy C-Means"),
Jumlah_Klaster = c(
length(unique(km_res$cluster)),
optimal_k,
length(unique(clusters(kmed_res))),
length(unique(db_valid)),
length(unique(fcm_res$cluster))
),
Silhouette_Score = c(
round(mean(sil_km[,3]), 4),
round(mean(sil_hc[,3]), 4),
round(mean(sil_kmed[,3]), 4),
sil_db_score,
round(mean(sil_fcm[,3]), 4)
),
Dunn_Index = c(dunn_km, dunn_hc, dunn_kmed, dunn_db, dunn_fcm)
)
cat(" Tabel Evaluasi 5 Metode Clustering \n")
print(tabel_eval)
Tabel Evaluasi 5 Metode Clustering
Metode Jumlah_Klaster Silhouette_Score Dunn_Index
1 K-Means 3 0.2849 0.2323
2 Hierarchical 3 0.2774 0.2286
3 K-Medians 3 0.2818 0.2286
4 DBSCAN 1 NA NA
5 Fuzzy C-Means 3 0.2849 0.2323
# Visualisasi perbandingan metrik
tabel_eval %>%
pivot_longer(cols = c(Silhouette_Score, Dunn_Index),
names_to = "Metrik", values_to = "Skor") %>%
filter(!is.na(Skor)) %>%
ggplot(aes(x = reorder(Metode, Skor), y = Skor, fill = Metode)) +
geom_col(show.legend = FALSE, width = 0.6) +
geom_text(aes(label = round(Skor, 4)),
hjust = -0.12, size = 3.3, fontface = "bold") +
facet_wrap(~Metrik, scales = "free_x",
labeller = labeller(Metrik = c(
Silhouette_Score = "Silhouette Score (↑ lebih baik)",
Dunn_Index = "Dunn Index (↑ lebih baik)"
))) +
coord_flip() +
scale_fill_manual(values = c(
"K-Means" = "#6C5CE7",
"Hierarchical" = "#00CEC9",
"K-Medians" = "#FDCB6E",
"DBSCAN" = "#74B9FF",
"Fuzzy C-Means" = "#E17055"
)) +
theme_minimal(base_size = 11) +
theme(
strip.text = element_text(face = "bold"),
panel.grid.minor = element_blank()
) +
labs(
title = "Perbandingan 5 Metode Clustering — Wine Dataset",
subtitle = "Nilai lebih tinggi menunjukkan kualitas klaster yang lebih baik",
x = NULL, y = "Skor"
)
Grafik ini membandingkan performa K-Means, Hierarchical, K-Medians, DBSCAN, dan Fuzzy C-Means menggunakan dua metrik: Silhouette Score dan Dunn Index.
K-Means & Fuzzy C-Means keluar sebagai pemenang dengan skor tertinggi (0.2849). Ini menunjukkan bahwa kedua metode ini menghasilkan pengelompokan yang paling solid dan terpisah dengan jelas. DBSCAN mendapatkan nilai N/A (tidak tersedia). DBSCAN gagal karena hanya menemukan 1 klaster besar, sehingga tidak bisa dihitung jarak antar-klasternya. Ini membuktikan bahwa data wine tidak memiliki "pulau kepadatan" yang terpisah jauh.
# Silhouette plot per metode (K-Means & FCM sebagai yang terbaik)
fviz_silhouette(sil_km) +
scale_fill_manual(values = c("#6C5CE7", "#E17055", "#00CEC9")) +
scale_color_manual(values = c("#6C5CE7", "#E17055", "#00CEC9")) +
theme_minimal() +
labs(title = "Silhouette Plot — K-Means (k=3)")
cluster size ave.sil.width
1 1 62 0.34
2 2 65 0.18
3 3 51 0.35
Grafik berbentuk seperti "sirip hiu" yang menyamping untuk Klaster 1, 2, dan 3. Interpretasi:
Klaster 1 & 3 memiliki "sirip" yang panjang dan tebal, artinya anggotanya sangat solid dan konsisten. Sementara Klaster 2 memiliki beberapa garis yang sangat pendek atau bahkan mengarah ke kiri (negatif). Ini adalah temuan penting: artinya ada beberapa sampel wine di Klaster 2 yang sebenarnya ambigu atau "salah masuk kamar" karena profil kimianya berada tepat di perbatasan antar jenis wine.
fviz_silhouette(sil_hc) +
scale_fill_manual(values = c("#6C5CE7", "#E17055", "#00CEC9")) +
scale_color_manual(values = c("#6C5CE7", "#E17055", "#00CEC9")) +
theme_minimal() +
labs(title = "Silhouette Plot — Hierarchical Clustering (k=3)")
cluster size ave.sil.width
1 1 64 0.33
2 2 58 0.19
3 3 56 0.31
Hasilnya sangat mirip dengan K-Means, namun dengan distribusi jumlah anggota yang sedikit berbeda (64, 58, 56). Rata-rata skor keseluruhannya (0.2774) sedikit di bawah K-Means. Ini membuktikan bahwa untuk dataset wine ini, pendekatan berbasis pusat (K-Means) sedikit lebih akurat daripada pendekatan berbasis pohon (Hierarchical).
Visualisasi PCA (Principal Component Analysis) digunakan untuk mereduksi dimensi data dari 13 variabel menjadi dua komponen utama, yaitu PC1 (Principal Component 1)dan PC2 (Principal Component 2), sehingga data dapat divisualisasikan dalam bentuk dua dimensi.
Reduksi dimensi ini bertujuan untuk mempermudah interpretasi pola dan sebaran klaster yang terbentuk dari hasil clustering. Dengan memproyeksikan data ke ruang berdimensi lebih rendah, hubungan antar data menjadi lebih mudah diamati secara visual.
Perlu diperhatikan bahwa PCA pada tahap ini hanya digunakan sebagai alat visualisasi, bukan sebagai metode untuk melakukan clustering. Proses pengelompokan tetap dilakukan menggunakan metode clustering yang telah diterapkan sebelumnya.
pca_res <- prcomp(df_scaled, center = FALSE, scale. = FALSE)
df_pca <- as.data.frame(pca_res$x[, 1:2])
var_explained <- summary(pca_res)$importance[2, 1:2]
cat(sprintf("PC1 menjelaskan : %.1f%% variansi\n", var_explained[1] * 100))
cat(sprintf("PC2 menjelaskan : %.1f%% variansi\n", var_explained[2] * 100))
cat(sprintf("Total 2 komponen: %.1f%% variansi\n", sum(var_explained) * 100))
PC1 menjelaskan : 36.2% variansi
PC2 menjelaskan : 19.2% variansi
Total 2 komponen: 55.4% variansi
# Kumpulkan semua label klaster ke df_pca
df_pca$KMeans <- as.factor(km_res$cluster)
df_pca$Hierarchical <- as.factor(hc_cluster)
df_pca$KMedians <- as.factor(clusters(kmed_res))
df_pca$DBSCAN <- as.factor(ifelse(db_res$cluster == 0, "Noise",
paste("K", db_res$cluster)))
df_pca$FuzzyCMeans <- as.factor(fcm_res$cluster)
# Warna custom
warna3 <- c("#6C5CE7", "#E17055", "#00CEC9")
warnaDB <- c("Noise" = "grey70", "K 1" = "#6C5CE7", "K 2" = "#E17055")
# Plot 1: K-Means
p1 <- ggplot(df_pca, aes(x = PC1, y = PC2, color = KMeans)) +
geom_point(alpha = 0.8, size = 2.5) +
stat_ellipse(level = 0.92, linewidth = 0.8) +
scale_color_manual(values = warna3) +
theme_minimal(base_size = 10) +
labs(title = "K-Means (k=3)", color = "Klaster",
subtitle = sprintf("PC1+PC2 = %.1f%%", sum(var_explained)*100))
# Plot 2: Hierarchical
p2 <- ggplot(df_pca, aes(x = PC1, y = PC2, color = Hierarchical)) +
geom_point(alpha = 0.8, size = 2.5) +
stat_ellipse(level = 0.92, linewidth = 0.8) +
scale_color_manual(values = warna3) +
theme_minimal(base_size = 10) +
labs(title = "Hierarchical (Ward, k=3)", color = "Klaster",
subtitle = "Metode hierarkis aglomeratif")
# Plot 3: K-Medians
p3 <- ggplot(df_pca, aes(x = PC1, y = PC2, color = KMedians)) +
geom_point(alpha = 0.8, size = 2.5) +
stat_ellipse(level = 0.92, linewidth = 0.8) +
scale_color_manual(values = warna3) +
theme_minimal(base_size = 10) +
labs(title = "K-Medians (k=3)", color = "Klaster",
subtitle = "Lebih robust terhadap outlier")
# Plot 4: DBSCAN
p4 <- ggplot(df_pca, aes(x = PC1, y = PC2, color = DBSCAN)) +
geom_point(alpha = 0.8, size = 2.5) +
scale_color_manual(values = warnaDB) +
theme_minimal(base_size = 10) +
labs(title = "DBSCAN (eps=3.0)", color = "Status",
subtitle = "Abu-abu = noise points")
# Plot 5: Fuzzy C-Means
p5 <- ggplot(df_pca, aes(x = PC1, y = PC2, color = FuzzyCMeans)) +
geom_point(alpha = 0.8, size = 2.5) +
stat_ellipse(level = 0.92, linewidth = 0.8) +
scale_color_manual(values = warna3) +
theme_minimal(base_size = 10) +
labs(title = "Fuzzy C-Means (k=3, m=2)", color = "Klaster",
subtitle = "Soft clustering — titik bisa ada di beberapa klaster")
# Tampilkan semua
library(gridExtra)
grid.arrange(p1, p2, p3, p4, p5, nrow = 2,
top = "Perbandingan Visualisasi 5 Metode Clustering — Ruang PCA")
Attaching package: ‘gridExtra’
The following object is masked from ‘package:dplyr’:
combine
PCA berhasil merangkum 55.4% informasi data, dan secara visual mengonfirmasi bahwa pemisahan klaster terjadi paling jelas di sepanjang sumbu PC1 (yang mewakili fitur-fitur utama seperti Flavanoids dan Total Phenols).
Metode K-Means digunakan sebagai acuan utama dalam analisis ini karena menunjukkan nilai evaluasi yang relatif lebih baik dibandingkan metode lainnya, meskipun perbedaannya tidak terlalu signifikan. Oleh karena itu, K-Means dipilih sebagai representasi untuk melakukan profiling terhadap masing-masing klaster yang terbentuk.
# Heatmap profiling lengkap — K-Means
profil_km %>%
pivot_longer(-klaster, names_to = "Fitur", values_to = "Nilai") %>%
ggplot(aes(x = klaster, y = Fitur, fill = Nilai)) +
geom_tile(color = "white", linewidth = 0.5) +
geom_text(aes(label = round(Nilai, 1)), size = 2.9, fontface = "bold") +
scale_fill_gradient2(
low = "#6A5ACD",
mid = "#F1F2F6",
high = "#20B2AA",
midpoint = median(unlist(profil_km[,-1]))
) +
theme_minimal(base_size = 10) +
theme(axis.text.x = element_text(face = "bold"),
panel.grid = element_blank()) +
labs(
title = "Heatmap Profiling Klaster K-Means",
subtitle = "Nilai rata-rata tiap fitur per klaster (skala data asli)",
x = "Klaster", y = NULL, fill = "Rata-rata"
)
Klaster 1 — Wine Tua / Berat:
Malic_Acid dan Color_Intensity tinggi →
warna pekat, rasa lebih asamFlavanoids rendah → kandungan antioksidan lebih
sedikitKlaster 2 — Wine Premium:
Alcohol dan Proline tertinggi di antara
semua klasterTotal_Phenols dan Flavanoids juga tinggi →
profil kimia paling kayaKlaster 3 — Wine Ringan / Segar:
Alcohol, Proline, dan
Color_Intensity terendahcat(" Ranking Metode Berdasarkan Silhouette Score \n")
tabel_eval_sorted <- tabel_eval[order(-tabel_eval$Silhouette_Score, na.last=TRUE), ]
print(tabel_eval_sorted)
best_method <- tabel_eval_sorted$Metode[1]
best_sil <- tabel_eval_sorted$Silhouette_Score[1]
cat(sprintf("\nMetode terbaik: %s (Silhouette = %.4f)\n", best_method, best_sil))
Ranking Metode Berdasarkan Silhouette Score
Metode Jumlah_Klaster Silhouette_Score Dunn_Index
1 K-Means 3 0.2849 0.2323
5 Fuzzy C-Means 3 0.2849 0.2323
3 K-Medians 3 0.2818 0.2286
2 Hierarchical 3 0.2774 0.2286
4 DBSCAN 1 NA NA
Metode terbaik: K-Means (Silhouette = 0.2849)
Catatan dari hasil clustering ini:
Secara keseluruhan, K-Means dan Hierarchical Clustering tampil paling konsisten di dataset wine ini. Keduanya menghasilkan 3 klaster yang selaras dengan asal 3 kultivar anggur — bukti bahwa struktur alami data memang mendukung k=3.
Fuzzy C-Means layak jadi pilihan alternatif, terutama kalau kita ingin tahu seberapa "yakin" suatu wine masuk ke satu klaster. Wine yang ada di perbatasan dua kultivar (kimia campuran) akan terlihat punya membership degree yang hampir sama di dua klaster.
DBSCAN kurang cocok di sini. Data wine tidak punya "pulau-pulau" kepadatan yang jelas — distribusinya lebih menyebar merata, sehingga DBSCAN kesulitan memisahkan 3 kelompok dan malah menghasilkan cuma 2 klaster + sedikit noise.
K-Medians hasilnya mirip K-Means karena setelah IQR Capping, outlier sudah terkontrol — jadi keunggulan robustness K-Medians tidak terlalu kelihatan di kasus ini.
Rekomendasi praktis: untuk klasifikasi wine berdasarkan profil kimia, K-Means dengan k=3 sudah cukup kuat dan mudah diinterpretasi. Kalau ingin eksplorasi lebih dalam (misalnya untuk data yang lebih berisik), bisa coba Gaussian Mixture Model (GMM) sebagai lanjutannya.
cat(" Ringkasan Akhir \n")
cat("Dataset : Wine (UCI) — 178 sampel, 13 fitur kimia\n")
cat("k optimal: 3 (Elbow + Silhouette + konteks 3 kultivar)\n")
cat("\nMetode | Silhouette | Dunn | Catatan\n")
cat(strrep("-", 55), "\n")
for (i in 1:nrow(tabel_eval_sorted)) {
r <- tabel_eval_sorted[i,]
catatan <- switch(r$Metode,
"K-Means" = "Best overall",
"Hierarchical" = "Sangat kompetitif, visual bagus",
"K-Medians" = "Mirip K-Means, lebih robust outlier",
"DBSCAN" = "Kurang cocok, data terlalu kontinu",
"Fuzzy C-Means" = "Baik untuk data ambigu"
)
cat(sprintf("%-14s| %-10s | %-7s | %s\n",
r$Metode,
ifelse(is.na(r$Silhouette_Score), "N/A", r$Silhouette_Score),
ifelse(is.na(r$Dunn_Index), "N/A", r$Dunn_Index),
catatan))
}
Ringkasan Akhir
Dataset : Wine (UCI) — 178 sampel, 13 fitur kimia
k optimal: 3 (Elbow + Silhouette + konteks 3 kultivar)
Metode | Silhouette | Dunn | Catatan
-------------------------------------------------------
K-Means | 0.2849 | 0.2323 | Best overall
Fuzzy C-Means | 0.2849 | 0.2323 | Baik untuk data ambigu
K-Medians | 0.2818 | 0.2286 | Mirip K-Means, lebih robust outlier
Hierarchical | 0.2774 | 0.2286 | Sangat kompetitif, visual bagus
DBSCAN | N/A | N/A | Kurang cocok, data terlalu kontinu