Pada supervised learning, menilai model itu mudah: bandingkan prediksi dengan label yang benar, hitung akurasinya, selesai. Clustering tidak punya kemewahan itu — label justru hal yang kita cari, jadi tidak ada jawaban untuk dibandingkan. Kalau K-Means membelah 20 pelanggan menjadi tiga cluster, angka mana yang bisa kita pegang untuk berkata “pembagian ini bagus”? Pada chapter ini kita membangun kotak peralatan evaluasi clustering: elbow method untuk memilih jumlah k, silhouette score untuk menilai kualitas pembagian, Davies-Bouldin sebagai pembanding, dan uji stabilitas antar-seed — lalu kita buktikan bahwa semua metrik itu tetap punya batasnya.

Tiga Keluarga Ukuran untuk Data Tanpa Label #

Evaluasi clustering terbagi dalam tiga keluarga, dan ketiganya menjawab pertanyaan yang berbeda:

KeluargaPertanyaan yang dijawabContoh alat
Internalseberapa rapat dan terpisahkah cluster menurut data itu sendiri?inertia, silhouette, Davies-Bouldin
Eksternalseberapa dekat hasil clustering dengan label acuan yang kita punya diam-diam?adjusted rand index (ARI)
Stabilitasapakah hasilnya bertahan bila proses diulang dengan titik awal berbeda?perbandingan label antar-seed

Metrik internal adalah peralatan utama kita sehari-hari karena tidak butuh label apa pun. Metrik eksternal hanya terpakai kalau kita kebetulan punya label untuk validasi — misalnya segmen member yang sudah diberi tim pemasaran — dan itu terjadi jarang. Stabilitas adalah uji paling murah sekaligus paling sering dilupakan: hasil yang berubah total setiap kali kode dijalankan tidak layak dipakai mengambil keputusan, betapapun bagus angka metriknya.

Elbow Method: Membaca Lengkungan Inertia #

Kita mulai dari masalah yang paling sering: memilih jumlah cluster k pada K-Means. Ingat dari chapter K-Means Clustering bahwa inertia_ adalah jumlah kuadrat jarak semua titik ke centroid clusternya — makin banyak cluster, makin kecil angkanya, karena tiap titik makin dekat ke centroid-nya. Kalau k terus dinaikkan sampai k = jumlah titik, inertia pasti nol — tetapi cluster sebanyak itu tidak berguna. Elbow method membaca titik jenuhnya: nilai k tempat penurunan inertia mulai melandai, seperti siku (elbow) lengan.

Kita pakai data yang sama dengan chapter K-Means — 20 pelanggan Toko Kopi Kita dengan fitur belanja dan frekuensi yang sudah distandarisasi — dan menghitung tiga metrik internal sekaligus untuk k = 2 sampai 6:

python
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score, davies_bouldin_score
from sklearn.preprocessing import StandardScaler

# 20 pelanggan Toko Kopi Kita (data yang sama dengan chapter K-Means Clustering)
pelanggan = pd.DataFrame({
    "belanja": [40, 55, 35, 60, 45, 50,
                150, 170, 160, 140, 175, 165,
                380, 420, 350, 400, 390, 360, 410, 370],
    "frekuensi": [2, 1, 3, 2, 4, 2,
                  11, 9, 12, 10, 8, 10,
                  6, 8, 7, 5, 9, 6, 7, 5],
})

X = StandardScaler().fit_transform(pelanggan[["belanja", "frekuensi"]])

print(" k  inertia   silhouette   davies-bouldin")
for k in range(2, 7):
    model = KMeans(n_clusters=k, random_state=42, n_init="auto").fit(X)
    print("%2d  %7.3f  %11.4f  %14.4f"
          % (k, model.inertia_,
             silhouette_score(X, model.labels_),
             davies_bouldin_score(X, model.labels_)))

Output yang Dihasilkan:

text
 k  inertia   silhouette   davies-bouldin
 2   21.956       0.4725          0.8589
 3    3.088       0.7362          0.3462
 4    2.102       0.6325          0.4691
 5    1.869       0.5937          0.5059
 6    1.466       0.5105          0.5575

Mari kita telaah baris-baris kode dan hasilnya:

  1. StandardScaler().fit_transform mengembalikan kedua fitur ke z-skor — syarat wajib dari chapter Pengenalan Clustering , karena inertia menjumlahkan jarak dan skala fitur menentukan jarak.
  2. Loop for k in range(2, 7) melatih K-Means lima kali dengan random_state=42, n_init="auto" agar tiap baris bisa direproduksi; hasil k di luar rentang 2-6 jarang menarik karena k=1 tidak bisa dinilai silhouette dan k yang mendekati jumlah titik jelas terlalu banyak.
  3. Kolom inertia memperlihatkan siku yang jelas: penurunan dari k=2 ke k=3 sangat curam (21,956 menjadi 3,088), lalu melandai tipis setelahnya — elbow berada di k=3.
  4. Kolom silhouette (makin besar makin baik, rentang -1 sampai 1) ikut memuncak di k=3 dengan 0,7362, dan kolom Davies-Bouldin (rasio sebaran dalam cluster terhadap jarak antar cluster; makin kecil makin baik) mencapai minimumnya di k=3 juga.

Tiga metrik yang sepakat seperti ini adalah skenario terbaik. Pada data nyata ketiganya sering berbeda pendapat — silhouette memuncak di k=4 sementara elbow tampak di k=3 — dan di situlah kita perlu pengetahuan domain untuk memutuskan.

Silhouette: Menilai Kualitas Per Titik #

Inertia selalu turun saat k naik sehingga tidak bisa berdiri sendiri. Silhouette score memakai ukuran yang berbeda dan tidak bergantung pada k: untuk setiap titik i, bandingkan jarak rata-rata ke tetangga sekampung dengan jarak rata-rata ke cluster lain terdekat.

text
a(i) = jarak rata-rata titik i ke sesama anggota clusternya sendiri
b(i) = jarak rata-rata titik i ke cluster TERDEKAT yang bukan miliknya
s(i) = (b(i) - a(i)) / max(a(i), b(i))

Titik yang nyaman di tengah clusternya punya a(i) kecil dan b(i) besar, sehingga s(i) mendekati 1. Titik yang bergantung di perbatasan dua cluster punya a(i) ≈ b(i) sehingga s(i) mendekati 0. Sebagai patokan umum untuk rata-rata silhouette:

Rentang rata-rataPembacaan
0,71 - 1,00struktur cluster kuat
0,51 - 0,70struktur cluster wajar
0,26 - 0,50struktur lemah — periksa ulang fitur dan k
di bawah 0,25tidak ada struktur yang berarti

Angka rata-rata saja bisa menutupi titik yang sesungguhnya tersesat. silhouette_samples menghitung s(i) per titik sehingga kita bisa menyortir siapa yang paling tidak yakin dengan keanggotaannya:

python
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_samples
from sklearn.preprocessing import StandardScaler

pelanggan = pd.DataFrame({
    "belanja": [40, 55, 35, 60, 45, 50,
                150, 170, 160, 140, 175, 165,
                380, 420, 350, 400, 390, 360, 410, 370],
    "frekuensi": [2, 1, 3, 2, 4, 2,
                  11, 9, 12, 10, 8, 10,
                  6, 8, 7, 5, 9, 6, 7, 5],
})

X = StandardScaler().fit_transform(pelanggan[["belanja", "frekuensi"]])
label = KMeans(n_clusters=3, random_state=42, n_init="auto").fit_predict(X)

pelanggan["cluster"] = label
pelanggan["silhouette"] = silhouette_samples(X, label).round(3)

print(pelanggan.sort_values("silhouette").head(5).to_string(index=False))

Output yang Dihasilkan:

text
 belanja  frekuensi  cluster  silhouette
     390          9        1       0.476
     175          8        0       0.512
     420          8        1       0.657
     160         12        0       0.673
     350          7        1       0.676

Bedah kodenya singkat karena alurnya sudah akrab: silhouette_samples(X, label) mengembalikan array s(i) untuk 20 titik, kita bulatkan tiga desimal, lalu sort_values + head(5) mengangkat lima titik dengan nilai terendah. Hasilnya mudah dibaca: pelanggan bersilhouette terendah (0,476) adalah yang belanja 390 dan frekuensi 9 — anggota cluster 1 (belanja besar) yang paling rajin datang, sehingga jaraknya ke cluster 0 (pelanggan rutin) juga dekat. Meski begitu, 0,476 masih jauh dari 0; tidak ada titik yang benar-benar mencurigakan. Kalau muncul nilai mendekati 0 atau negatif, titik itu layak diperiksa satu per satu — terlalu sering ia pencilan yang dipoles masuk cluster atau sinyal bahwa k kurang tepat.

Uji Stabilitas: Ulangi dengan Seed Berbeda #

Metrik internal yang tinggi masih menyisakan satu kebocoran: hasil itu mungkin kebetulan dari satu titik awal yang beruntung. K-Means dengan n_init=1 hanya menjalankan satu inisialisasi; jika seed diganti, hasil bisa berbeda. Uji stabilitas memanfaatkan hal ini dengan sengaja: latih ulang model dengan beberapa seed, lalu ukur kesepakatan labelnya dengan adjusted rand index (ARI) — angka 1,00 berarti pembagian identik, 0,00 berarti seacak tebakan.

python
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.metrics import adjusted_rand_score, silhouette_score
from sklearn.preprocessing import StandardScaler

pelanggan = pd.DataFrame({
    "belanja": [40, 55, 35, 60, 45, 50,
                150, 170, 160, 140, 175, 165,
                380, 420, 350, 400, 390, 360, 410, 370],
    "frekuensi": [2, 1, 3, 2, 4, 2,
                  11, 9, 12, 10, 8, 10,
                  6, 8, 7, 5, 9, 6, 7, 5],
})

X = StandardScaler().fit_transform(pelanggan[["belanja", "frekuensi"]])
acuan = KMeans(n_clusters=3, random_state=42, n_init=1).fit_predict(X)

print("seed  silhouette  ARI terhadap seed 42")
for seed in [1, 7, 42]:
    label = KMeans(n_clusters=3, random_state=seed, n_init=1).fit_predict(X)
    print("%4d  %10.4f  %9.2f" % (seed, silhouette_score(X, label),
                                  adjusted_rand_score(acuan, label)))

Output yang Dihasilkan:

text
seed  silhouette  ARI terhadap seed 42
   1      0.7362       1.00
   7      0.7362       1.00
  42      0.7362       1.00

Tiga seed yang berbeda menghasilkan pembagian yang identik (ARI 1,00) dengan silhouette yang sama persis. Struktur tiga gumpalan pada data ini begitu kuat sehingga titik awal mana pun mendarat di solusi yang sama — itulah arti clustering yang stabil. Bayangkan kebalikannya: ARI yang bergelantungan antara 1,00 dan 0,48 antar-seed. Pembagian seperti itu tidak boleh dilaporkan apa adanya; kita perlu mengubah fitur, menaikkan n_init, atau menerima bahwa strukturnya memang rapuh.

Batas Metrik Internal: Ketika Angka Menyesatkan #

Semua metrik internal berbagi satu asumsi diam-diam: cluster yang baik itu rapat dan berjauhan — kebayangkan gumpalan bulat. Pada data berbentuk lain, metrik bisa memilih jawaban yang salah. Kita buktikan dengan data dua bulan sabit dari chapter Density-Based Clustering , kali ini tanpa pencilan:

python
import numpy as np
from sklearn.cluster import DBSCAN, KMeans
from sklearn.datasets import make_moons
from sklearn.metrics import silhouette_score

X, bulan = make_moons(n_samples=60, noise=0.06, random_state=42)

label_km = KMeans(n_clusters=2, random_state=42, n_init="auto").fit_predict(X)
label_db = DBSCAN(eps=0.35, min_samples=5).fit_predict(X)

def kemurnian(label):
    # rata-rata pangsa bulan mayoritas di dalam tiap cluster
    murni = []
    for c in [0, 1]:
        anggota = label == c
        pangsa = np.bincount(bulan[anggota], minlength=2).max() / anggota.sum()
        murni.append(pangsa)
    return sum(murni) / len(murni)

print("K-Means : silhouette %.4f | kemurnian rata-rata %.0f%%"
      % (silhouette_score(X, label_km), 100 * kemurnian(label_km)))
print("DBSCAN  : silhouette %.4f | kemurnian rata-rata %.0f%%"
      % (silhouette_score(X, label_db), 100 * kemurnian(label_db)))

Output yang Dihasilkan:

text
K-Means : silhouette 0.4840 | kemurnian rata-rata 75%
DBSCAN  : silhouette 0.3010 | kemurnian rata-rata 100%

Mari kita telaah kode dan hasilnya:

  1. make_moons(n_samples=60, noise=0.06, random_state=42) menghasilkan 60 titik dua bulan sabit plus label bulan asalnya (bulan) — label ini tidak dipakai algoritma mana pun, hanya menjadi hakim saat mengaudit isi cluster.
  2. Fungsi kemurnian menghitung pangsa bulan mayoritas di dalam tiap cluster lalu merata-ratakannya; angka 100% berarti setiap cluster murni berisi satu bulan.
  3. Baris K-Means: silhouette 0,4840 tampak lumayan, tetapi kemurnian hanya 75% — tiap potong kiri-kanan mencampur kedua bulan, persis temuan chapter DBSCAN.
  4. Baris DBSCAN: kemurnian 100% (satu bulan satu cluster) dengan silhouette 0,3010 — justru kalah angka dari K-Means.

Ironinya telanjang: DBSCAN memisahkan kedua bulan dengan sempurna, tetapi silhouette menghukumnya karena bentuk bulan sabit membuat titik di ujung lengkung berjauhan dari sesama anggotanya. Algoritma yang benar bentuknya kalah angka dari algoritma yang keliru.

Tiga keterbatasan ini perlu kita ingat setiap kali membaca tabel metrik:

  • Metrik internal mengukur geometri, bukan kebenaran. Tinggi untuk gumpalan bulat, rendah untuk bentuk memanjang atau lengkung — terlepas dari mana yang benar secara domain.
  • Elbow sering kabur. Pada data nyata lengkungannya jarang sesiku data 20 pelanggan kita; dua analis bisa memilih k berbeda dari grafik yang sama.
  • Satu metrik tidak pernah cukup. Praktik yang sehat menggabungkan elbow, silhouette, stabilitas, dan — yang paling menentukan — pemeriksaan apakah profil tiap cluster masuk akal bagi orang yang mengenal datanya.

Rangkuman #

  • Evaluasi clustering datang dalam tiga keluarga: internal (inertia, silhouette, Davies-Bouldin), eksternal (ARI terhadap label acuan), dan stabilitas (kesepakatan hasil antar-seed).
  • Elbow method membaca titik jenuh penurunan inertia; pada 20 pelanggan kafe, siku, puncak silhouette (0,7362), dan minimum Davies-Bouldin (0,3462) sama-sama menunjuk k=3.
  • silhouette_samples menilai tiap titik dengan rumus (b-a)/max(a,b); nilai mendekati 0 atau negatif menandai titik perbatasan yang layak diperiksa.
  • Uji stabilitas dengan beberapa seed memberi kepercayaan bahwa hasil bukan kebetulan titik awal; ARI 1,00 antar-seed berarti pembagian kokoh.
  • Metrik internal berasumsi cluster bulat: pada data bulan sabit, silhouette memilih K-Means (salah bentuk) di atas DBSCAN (benar bentuk) — angka selalu dibaca bersama konteks.

Dengan cara menilai clustering di tangan, alat unsupervised kita belum lengkap: sepanjang section ini semua data dilihat dalam dua fitur yang kebetulan muat di kertas grafik. Data nyata punya belasan fitur, dan struktur yang tersembunyi di dalamnya sulit dilihat kalau kita tidak bisa menggambarkannya. Chapter berikutnya memperkenalkan teknik yang mencari arah pandang terbaik dari data itu sendiri: Principal Component Analysis. Lanjut ke chapter Principal Component Analysis .

Referensi #

  1. scikit-learn — Clustering
  2. scikit-learn — Preprocessing Data