1. Pendahuluan

Dataset

Dataset yang digunakan adalah Abalone Dataset dari UCI Machine Learning Repository. Abalone adalah sejenis siput laut yang umurnya dapat ditentukan dari jumlah cincin (rings) pada cangkangnya — mirip seperti menghitung lingkaran pohon.

Dataset ini memiliki 4.177 observasi dengan 9 variabel:

Variabel Keterangan
Sex Jenis kelamin (M/F/I)
Length Panjang cangkang (mm)
Diameter Diameter cangkang (mm)
Height Tinggi cangkang (mm)
Whole_weight Berat total (gram)
Shucked_weight Berat daging (gram)
Viscera_weight Berat isi perut setelah dikeringkan (gram)
Shell_weight Berat cangkang setelah dikeringkan (gram)
Rings Jumlah cincin -> indikator umur

Tujuan

Menerapkan 2 metode clustering (K-Means dan PAM/K-Medoids) pada data Abalone, kemudian memvalidasi hasilnya menggunakan tiga jenis kriteria validasi:

  • Validasi Eksternal -> Cluster Accuracy, Cluster Purity, Rand Index
  • Validasi Internal -> Silhouette Width, Shadow Value
  • Validasi Relatif -> Consensus Matrix (Bootstrap)

2. Load Library

library(factoextra)   # visualisasi clustering
library(ggplot2)      # plotting
library(cluster)      # algoritma PAM dan silhouette
library(clusterCrit)  # Rand Index (extCriteria)
library(fpc)          # Shadow Value (cluster.stats)
library(kmed)         # distNumeric, clustboot, consensusmatrix, clustheatmap

3. Load & Pra-pemrosesan Data

Mengunduh Data

url <- "https://archive.ics.uci.edu/ml/machine-learning-databases/abalone/abalone.data"
abalone <- read.csv(url, header = FALSE)

colnames(abalone) <- c("Sex", "Length", "Diameter", "Height", "Whole_weight",
                       "Shucked_weight", "Viscera_weight", "Shell_weight", "Rings")

head(abalone)
##   Sex Length Diameter Height Whole_weight Shucked_weight Viscera_weight
## 1   M  0.455    0.365  0.095       0.5140         0.2245         0.1010
## 2   M  0.350    0.265  0.090       0.2255         0.0995         0.0485
## 3   F  0.530    0.420  0.135       0.6770         0.2565         0.1415
## 4   M  0.440    0.365  0.125       0.5160         0.2155         0.1140
## 5   I  0.330    0.255  0.080       0.2050         0.0895         0.0395
## 6   I  0.425    0.300  0.095       0.3515         0.1410         0.0775
##   Shell_weight Rings
## 1        0.150    15
## 2        0.070     7
## 3        0.210     9
## 4        0.155    10
## 5        0.055     7
## 6        0.120     8

Eksplorasi Awal

dim(abalone)
## [1] 4177    9
summary(abalone[, 2:9])
##      Length         Diameter          Height        Whole_weight   
##  Min.   :0.075   Min.   :0.0550   Min.   :0.0000   Min.   :0.0020  
##  1st Qu.:0.450   1st Qu.:0.3500   1st Qu.:0.1150   1st Qu.:0.4415  
##  Median :0.545   Median :0.4250   Median :0.1400   Median :0.7995  
##  Mean   :0.524   Mean   :0.4079   Mean   :0.1395   Mean   :0.8287  
##  3rd Qu.:0.615   3rd Qu.:0.4800   3rd Qu.:0.1650   3rd Qu.:1.1530  
##  Max.   :0.815   Max.   :0.6500   Max.   :1.1300   Max.   :2.8255  
##  Shucked_weight   Viscera_weight    Shell_weight        Rings       
##  Min.   :0.0010   Min.   :0.0005   Min.   :0.0015   Min.   : 1.000  
##  1st Qu.:0.1860   1st Qu.:0.0935   1st Qu.:0.1300   1st Qu.: 8.000  
##  Median :0.3360   Median :0.1710   Median :0.2340   Median : 9.000  
##  Mean   :0.3594   Mean   :0.1806   Mean   :0.2388   Mean   : 9.934  
##  3rd Qu.:0.5020   3rd Qu.:0.2530   3rd Qu.:0.3290   3rd Qu.:11.000  
##  Max.   :1.4880   Max.   :0.7600   Max.   :1.0050   Max.   :29.000

Scaling Data

Clustering berbasis jarak (seperti K-Means dan PAM) sangat sensitif terhadap perbedaan skala antar variabel. Misalnya, variabel berat dalam gram bisa memiliki nilai jauh lebih besar dibanding panjang dalam mm. Jika tidak di-scale, variabel dengan skala besar akan mendominasi perhitungan jarak.

Oleh karena itu, kita lakukan standardisasi (z-score scaling) menggunakan fungsi scale(), sehingga setiap variabel memiliki mean = 0 dan standar deviasi = 1.

# Ambil fitur numerik saja (buang Sex dan Rings)
data_cluster <- abalone[, 2:8]

# Scaling
data_scaled <- scale(data_cluster)

Sampling Data

Untuk efisiensi komputasi (terutama saat menghitung matriks jarak dan bootstrap), kita ambil sampel 1.000 baris secara acak.

set.seed(123)
idx         <- sample(1:nrow(data_scaled), 1000)
data_sample <- data_scaled[idx, ]

Membuat True Label

Untuk validasi eksternal, kita butuh label “kelas yang sebenarnya”. Karena Abalone tidak punya label kelas, kita gunakan kolom Rings sebagai proksi umur, lalu dibagi menjadi 3 kelompok:

  • Kelas 1 (Muda): Rings ≤ 8
  • Kelas 2 (Dewasa): 8 < Rings ≤ 11
  • Kelas 3 (Tua): Rings > 11
rings_sample <- abalone$Rings[idx]
true_label   <- cut(rings_sample, breaks = c(0, 8, 11, Inf), labels = c(1, 2, 3))
true_label   <- as.integer(true_label)

table(true_label)
## true_label
##   1   2   3 
## 350 424 226

4. Clustering — 2 Metode

Metode 1: K-Means

K-Means bekerja dengan cara:

  1. Tentukan jumlah klaster k (kita gunakan k=3)
  2. Inisialisasi k centroid secara acak
  3. Setiap titik data diassign ke centroid terdekat
  4. Hitung ulang posisi centroid berdasarkan rata-rata anggotanya
  5. Ulangi langkah 3–4 hingga centroid tidak berubah lagi

Kelebihan: Cepat dan sederhana
Kekurangan: Sensitif terhadap outlier karena menggunakan rata-rata

set.seed(123)
kmeans_res     <- kmeans(data_sample, centers = 3, nstart = 25)
kmeans_cluster <- kmeans_res$cluster

# Distribusi anggota tiap cluster
table(kmeans_cluster)
## kmeans_cluster
##   1   2   3 
## 246 320 434

Metode 2: PAM (K-Medoids)

PAM (Partitioning Around Medoids) mirip K-Means, tetapi:

  • Pusat klaster adalah objek nyata dari data (disebut medoid), bukan rata-rata
  • Lebih robust terhadap outlier karena tidak menggunakan mean
  • Lebih lambat dari K-Means karena harus mencari medoid terbaik dari semua objek
pam_res     <- pam(data_sample, k = 3)
pam_cluster <- pam_res$clustering
pam_medoids <- pam_res$id.med

# Distribusi anggota tiap cluster
table(pam_cluster)
## pam_cluster
##   1   2   3 
## 298 387 315

5. Visualisasi Hasil Clustering

Karena data memiliki 7 dimensi, kita gunakan PCA (Principal Component Analysis) untuk mereduksi ke 2 dimensi agar bisa divisualisasikan. Fungsi fviz_cluster() melakukan ini secara otomatis.

p1 <- fviz_cluster(kmeans_res, data = data_sample, geom = "point",
                   ellipse.type = "convex") +
  theme_bw() +
  labs(title = "Hasil Clustering dengan K-Means (k=3)")
print(p1)

p2 <- fviz_cluster(pam_res, geom = "point", ellipse.type = "convex") +
  theme_bw() +
  labs(title = "Hasil Clustering dengan PAM/K-Medoids (k=3)")
print(p2)


6. Validasi Eksternal

Validasi eksternal membandingkan hasil clustering dengan label kelas yang sudah diketahui (gold standard). Dalam kasus ini, kita gunakan pengelompokan umur Abalone berdasarkan Rings sebagai acuan.

Contingency Table

Tabel kontingensi menunjukkan berapa banyak objek dari tiap true label yang masuk ke tiap cluster.

tab_kmeans <- table(kmeans_cluster, true_label)
tab_pam    <- table(pam_cluster,    true_label)

cat("=== Contingency Table K-Means ===\n")
## === Contingency Table K-Means ===
print(tab_kmeans)
##               true_label
## kmeans_cluster   1   2   3
##              1   7 138 101
##              2 247  57  16
##              3  96 229 109
cat("\n=== Contingency Table PAM ===\n")
## 
## === Contingency Table PAM ===
print(tab_pam)
##            true_label
## pam_cluster   1   2   3
##           1 234  50  14
##           2 101 191  95
##           3  15 183 117

Cluster Accuracy

Akurasi clustering dihitung dengan asumsi nilai diagonal tabel kontingensi adalah pencocokan yang optimal:

\[A = \sum_{i=1}^{k} \frac{n_{ii}}{n_{..}}\]

di mana \(n_{ii}\) adalah nilai diagonal baris ke-\(i\) kolom ke-\(i\), dan \(n_{..}\) adalah total observasi.

accuracy_kmeans <- sum(diag(tab_kmeans)) / sum(tab_kmeans)
accuracy_pam    <- sum(diag(tab_pam))    / sum(tab_pam)

cat("Cluster Accuracy K-Means :", round(accuracy_kmeans, 4), "\n")
## Cluster Accuracy K-Means : 0.173
cat("Cluster Accuracy PAM     :", round(accuracy_pam,    4), "\n")
## Cluster Accuracy PAM     : 0.542

Cluster Purity

Purity mengukur seberapa homogen tiap cluster terhadap label aslinya. Untuk tiap cluster, diambil kelas mayoritas, lalu dijumlahkan:

\[P = \sum_{i=1}^{k} \frac{n_i}{n_{..}} \left( \max_{j \in K} \frac{n_{ij}}{n_i} \right)\]

Nilai mendekati 1 berarti setiap cluster didominasi oleh satu kelas → clustering bagus.

purity_kmeans <- sum(apply(tab_kmeans, 1, max)) / sum(tab_kmeans)
purity_pam    <- sum(apply(tab_pam,    1, max)) / sum(tab_pam)

cat("Cluster Purity K-Means :", round(purity_kmeans, 4), "\n")
## Cluster Purity K-Means : 0.614
cat("Cluster Purity PAM     :", round(purity_pam,    4), "\n")
## Cluster Purity PAM     : 0.608

Rand Index

Rand Index mengukur proporsi pasang objek yang dikelompokkan konsisten antara hasil clustering dan true label.

\[RI = \frac{B}{B + D}\]

  • \(B\) = pasang objek yang sama-sama satu cluster DI KEDUANYA, atau berbeda cluster DI KEDUANYA
  • \(D\) = pasang objek yang inkonsisten

Nilai mendekati 1 → sangat konsisten dengan true label.

ri_kmeans <- extCriteria(as.integer(kmeans_cluster), as.integer(true_label), "Rand")
ri_pam    <- extCriteria(as.integer(pam_cluster),    as.integer(true_label), "Rand")

cat("Rand Index K-Means :", round(ri_kmeans$rand, 4), "\n")
## Rand Index K-Means : 0.6297
cat("Rand Index PAM     :", round(ri_pam$rand,    4), "\n")
## Rand Index PAM     : 0.6296

7. Validasi Internal

Validasi internal tidak membutuhkan true label. Kualitas clustering dinilai dari struktur data itu sendiri — seberapa kompak isi tiap cluster dan seberapa jauh antar cluster.

Silhouette Width

Silhouette value untuk tiap objek \(i\) dihitung sebagai:

\[s(i) = \frac{b_i - a_i}{\max(a_i, b_i)}\]

di mana: - \(a_i\) = rata-rata jarak objek \(i\) ke semua objek di clusternya sendiri (kompakness) - \(b_i\) = rata-rata jarak objek \(i\) ke semua objek di cluster terdekat lainnya (separation)

Interpretasi: - Nilai mendekati +1 → objek berada di cluster yang tepat - Nilai mendekati 0 → objek berada di perbatasan dua cluster - Nilai mendekati −1 → objek mungkin salah cluster

dist_matrix <- dist(data_sample)

# Silhouette K-Means
sil_kmeans     <- silhouette(kmeans_cluster, dist_matrix)
avg_sil_kmeans <- mean(sil_kmeans[, 3])
cat("Avg Silhouette Width K-Means :", round(avg_sil_kmeans, 4), "\n")
## Avg Silhouette Width K-Means : 0.4505
# Silhouette PAM
sil_pam     <- silhouette(pam_cluster, dist_matrix)
avg_sil_pam <- mean(sil_pam[, 3])
cat("Avg Silhouette Width PAM     :", round(avg_sil_pam,    4), "\n")
## Avg Silhouette Width PAM     : 0.4411
plot(sil_kmeans, main = "Silhouette Plot - K-Means", col = "steelblue")

plot(sil_pam, main = "Silhouette Plot - PAM", col = "firebrick4")

Shadow Value

Shadow value adalah ukuran yang mirip dengan silhouette, namun berbasis centroid/medoid (bukan rata-rata jarak ke semua anggota cluster). Dihitung sebagai:

\[sh(i) = \frac{2 \cdot d(i, c(i))}{d(i, c(i)) + d(i, \hat{c}(i))}\]

di mana \(c(i)\) adalah centroid terdekat dan \(\hat{c}(i)\) adalah centroid terdekat kedua.

Interpretasi kebalikan dari silhouette: - Nilai mendekati 0 → cluster terpisah dengan baik - Nilai mendekati 1 → pemisahan cluster buruk

shadowval_kmeans <- cluster.stats(dist_matrix, kmeans_cluster)
shadowval_pam    <- cluster.stats(dist_matrix, pam_cluster)

cat("Shadow Value K-Means :", round(shadowval_kmeans$avg.silwidth, 4), "\n")
## Shadow Value K-Means : 0.4505
cat("Shadow Value PAM     :", round(shadowval_pam$avg.silwidth,    4), "\n")
## Shadow Value PAM     : 0.4411

8. Validasi Relatif (Consensus Matrix)

Validasi relatif menguji stabilitas hasil clustering. Ideanya: jika clustering benar-benar menemukan struktur yang ada di data, maka hasil clustering seharusnya konsisten walaupun datanya sedikit berubah (lewat bootstrap sampling).

Cara kerja: 1. Ambil sampel bootstrap dari data sebanyak 50 kali 2. Lakukan clustering pada tiap sampel 3. Hitung seberapa sering sepasang objek masuk ke cluster yang sama → disimpan dalam consensus matrix (n × n) 4. Visualisasikan dengan heatmap — jika terbentuk blok diagonal yang jelas → clustering stabil

num_mat <- as.matrix(data_sample)
mrwdist <- distNumeric(num_mat, num_mat)

# Fungsi PAM untuk clustboot
pamfunc <- function(x, nclust) {
  res <- pam(as.dist(x), k = nclust)
  return(res$clustering)
}

# Bootstrap 50 replikasi
set.seed(123)
pam_bootstrap <- clustboot(mrwdist, nclust = 3, nboot = 50, algorithm = pamfunc)

# Ordering dengan Ward linkage
wardorder <- function(x, nclust) {
  res    <- hclust(as.dist(x), method = "ward.D2")
  member <- cutree(res, nclust)
  return(member)
}

# Consensus matrix & heatmap
consensus_pam <- consensusmatrix(pam_bootstrap, nclust = 3, wardorder)
clustheatmap(consensus_pam, "Abalone data - PAM, ordered by Ward linkage")

Blok diagonal yang jelas pada heatmap menunjukkan bahwa hasil clustering PAM stabil dan konsisten di berbagai bootstrap sample.


9. Ringkasan & Kesimpulan

Tabel Perbandingan

hasil <- data.frame(
  Metode         = c("K-Means", "PAM"),
  Accuracy       = round(c(accuracy_kmeans,                accuracy_pam),               4),
  Purity         = round(c(purity_kmeans,                  purity_pam),                 4),
  Rand_Index     = round(c(ri_kmeans$rand,                 ri_pam$rand),                4),
  Avg_Silhouette = round(c(avg_sil_kmeans,                 avg_sil_pam),                4),
  Shadow_Value   = round(c(shadowval_kmeans$avg.silwidth,  shadowval_pam$avg.silwidth), 4)
)

knitr::kable(hasil, caption = "Perbandingan Hasil Validasi K-Means vs PAM")
Perbandingan Hasil Validasi K-Means vs PAM
Metode Accuracy Purity Rand_Index Avg_Silhouette Shadow_Value
K-Means 0.173 0.614 0.6297 0.4505 0.4505
PAM 0.542 0.608 0.6296 0.4411 0.4411

Kesimpulan

# Skor gabungan (Accuracy, Purity, Rand, Silhouette → makin tinggi makin baik)
skor_kmeans <- mean(c(accuracy_kmeans, purity_kmeans, ri_kmeans$rand, avg_sil_kmeans))
skor_pam    <- mean(c(accuracy_pam,    purity_pam,    ri_pam$rand,    avg_sil_pam))

cat("Skor rata-rata K-Means :", round(skor_kmeans, 4), "\n")
## Skor rata-rata K-Means : 0.4668
cat("Skor rata-rata PAM     :", round(skor_pam,    4), "\n\n")
## Skor rata-rata PAM     : 0.5552
cat("=== KESIMPULAN ===\n")
## === KESIMPULAN ===
if (skor_pam > skor_kmeans) {
  cat("PAM (K-Medoids) menghasilkan clustering yang lebih baik\n")
  cat("berdasarkan rata-rata skor validasi eksternal dan internal.\n\n")
  cat("PAM lebih cocok untuk data Abalone karena lebih robust\n")
  cat("terhadap outlier dibanding K-Means.\n")
} else {
  cat("K-Means menghasilkan clustering yang lebih baik\n")
  cat("berdasarkan rata-rata skor validasi eksternal dan internal.\n\n")
  cat("K-Means lebih efisien secara komputasi dan menghasilkan\n")
  cat("cluster yang lebih kompak pada data Abalone ini.\n")
}
## PAM (K-Medoids) menghasilkan clustering yang lebih baik
## berdasarkan rata-rata skor validasi eksternal dan internal.
## 
## PAM lebih cocok untuk data Abalone karena lebih robust
## terhadap outlier dibanding K-Means.

Cara membaca hasil validasi:

Metrik Lebih baik jika…
Accuracy Makin tinggi
Purity Makin tinggi
Rand Index Makin tinggi (maks = 1)
Avg Silhouette Makin tinggi (maks = 1)
Shadow Value Makin rendah (min = 0)