Kita kini memiliki tiga algoritme clustering dengan cara kerja dan asumsi yang berbeda — tetapi belum punya cara menilai hasilnya secara adil. Tanpa label kebenaran, akurasi tidak bisa dihitung; memeriksa profil klaster satu per satu melelahkan dan subjektif. Indeks silhouette mengisi kekosongan itu dengan satu pertanyaan sederhana untuk setiap titik: apakah titik ini lebih dekat ke klasternya sendiri dibanding ke klaster tetangga terdekat? Jawaban tiap titik dirata-ratakan menjadi satu angka yang bisa membandingkan k, algoritme, bahkan preprocessing yang berbeda. Chapter ini membedah rumusnya sampai hitungan tangan — data yang dipakai masih 120 pelanggan toko ritel dari chapter k-Means.
Rumus: Seberapa Nyaman Sebuah Titik #
Untuk sebuah titik i, silhouette menghitung dua jarak rata-rata:
a(i) = jarak rata-rata i ke SELURUH titik lain di klasternya sendiri
b(i) = jarak rata-rata i ke titik-titik klaster lain yang paling dekat
s(i) = (b(i) - a(i)) / max(a(i), b(i))Nilai a kecil berarti klaster sendiri rapat; nilai b besar berarti klaster tetangga jauh. Pembagi max(a, b) menjaga hasil selalu di rentang [-1, 1]:
| Nilai s(i) | Makna |
|---|---|
| Mendekati 1 | Titik jauh lebih dekat ke klasternya sendiri — penempatan kokoh |
| Mendekati 0 | a dan b nyaris sama — titik berdiri di perbatasan dua klaster |
| Negatif | Titik lebih dekat ke klaster tetangga — kemungkinan salah penempatan |
Rata-rata s(i) seluruh titik inilah yang dilaporkan silhouette_score. Skor rata-rata tinggi berarti klaster rapat sekaligus saling terpisah — dua sisi kualitas yang inertia (hanya mengukur kerapatan) tidak pernah lihat.
Silhouette versus Inertia untuk Memilih k #
Bab k-Means sudah memperlihatkan kelemahan mendasar inertia: nilainya selalu turun setiap k ditambah, sampai k = n mencapai nol — jadi ia tidak bisa memilih k sendirian, dan posisi “siku” elbow sering kabur. Silhouette tidak punya kewajiban monoton itu: klaster baru yang hanya membelah-belah gumpalan utuh akan menghasilkan titik perbatasan dengan s kecil atau negatif, sehingga rata-ratanya turun. Pada data kita, sweep k = 2..6 memperlihatkan kontras keduanya dalam satu tabel: inertia turun terus dari 815 sampai 146, sementara silhouette memuncak di k=3 (0,676) lalu merosot.
Perbandingan yang adil menuntut dua syarat. Pertama, data yang sama — skor silhouette antar dua dataset berbeda tidak bisa dibandingkan karena skala jarannya berbeda. Kedua, scaling yang sama — silhouette dihitung dari jarak, dan fitur berskala besar akan mendominasi jarak persis seperti pada KNN, k-means, dan DBSCAN. Bandingkan dua konfigurasi hanya saat keduanya memakai pipeline yang sama dari data mentah yang sama.
Asumsi yang Perlu Dijaga #
Silhouette punya bias sendiri: ia menyukai klaster bulat dan padat. Pada data dengan klaster memanjang atau berlapis — tepat wilayah kekuatan DBSCAN — skor bisa tampak buruk padahal segmentasinya benar: titik-titik ujung sabit berjarak jauh dari rata-rata klasternya sehingga a besar dan s turun, dan titik noise yang justru berhasil diidentifikasi DBSCAN akan dihukum sebagai penyimpangan. Anggap silhouette sebagai penilai yang cerdas namun berprasangka konveks: andalkan ia untuk membandingkan k-means dan Ward, periksa ulang dengan penilaian domain saat algoritme Anda berbasis kepadatan.
Praktik: Verifikasi Rumus Titik demi Titik #
# Evaluasi clustering dengan silhouette: rumus, sweep k, dan verifikasi manual.
# Data: 120 pelanggan toko ritel (sama dengan chapter k-Means Clustering).
import numpy as np
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.metrics import silhouette_samples, silhouette_score
PUSAT_SEG = [[2.0, 2.0], [7.0, 4.0], [5.0, 9.0]]
X, _ = make_blobs(
n_samples=120, centers=PUSAT_SEG, cluster_std=1.0, random_state=42
)
# 1) Sweep k=2..6: silhouette naik-turun, inertia monoton turun.
hasil = []
for k in range(2, 7):
km = KMeans(n_clusters=k, n_init="auto", random_state=42).fit(X)
hasil.append((k, silhouette_score(X, km.labels_), km.inertia_))
best_k = max(hasil, key=lambda baris: baris[1])[0]
print("Sweep k=2..6 (data 120 pelanggan):")
print(" k silhouette inertia")
for k, s, inert in hasil:
penanda = " <- silhouette tertinggi" if k == best_k else ""
print(f" {k} {s:.3f} {inert:>8.3f}{penanda}")
# 2) Bedah rumus untuk satu titik: yang paling ragu-ragu.
model3 = KMeans(n_clusters=3, n_init="auto", random_state=42).fit(X)
label = model3.labels_
samples = silhouette_samples(X, label)
i = int(np.argmin(samples))
print(f"\nBedah rumus silhouette untuk satu titik (indeks {i}, klaster {label[i]}):")
jarak = np.sqrt(((X - X[i]) ** 2).sum(axis=1))
a = jarak[label == label[i]].sum() / ((label == label[i]).sum() - 1)
print(f" jarak rata-rata ke klaster sendiri (a) : {a:.3f}")
for c in range(3):
if c != label[i]:
print(f" jarak rata-rata ke klaster {c}: {jarak[label == c].mean():.3f}")
b = min(jarak[label == c].mean() for c in range(3) if c != label[i])
print(f" klaster lain terdekat (b) : {b:.3f}")
s_manual = (b - a) / max(a, b)
print(f" s = (b - a) / max(a, b) = ({b:.3f} - {a:.3f}) / {b:.3f} = {s_manual:.3f}")
print(f" silhouette_samples[{i}] dari sklearn : {samples[i]:.3f}")
# 3) Titik kontras: yang paling kokoh di dalam klasternya.
j = int(np.argmax(samples))
jarak_j = np.sqrt(((X - X[j]) ** 2).sum(axis=1))
a_j = jarak_j[label == label[j]].sum() / ((label == label[j]).sum() - 1)
b_j = min(jarak_j[label == c].mean() for c in range(3) if c != label[j])
s_j = (b_j - a_j) / max(a_j, b_j)
print(f"\nTitik kontras (indeks {j}, klaster {label[j]}): "
f"a = {a_j:.3f}, b = {b_j:.3f}")
print(f" s manual = {s_j:.3f} | silhouette_samples[{j}] = {samples[j]:.3f}")
# 4) Rata-rata seluruh titik == silhouette_score.
print(f"\nRata-rata silhouette seluruh titik : {samples.mean():.3f}")
print(f"silhouette_score (API) : {silhouette_score(X, label):.3f}")Output yang Dihasilkan:
Sweep k=2..6 (data 120 pelanggan):
k silhouette inertia
2 0.534 815.524
3 0.676 220.353 <- silhouette tertinggi
4 0.553 191.065
5 0.430 167.398
6 0.430 145.861
Bedah rumus silhouette untuk satu titik (indeks 57, klaster 0):
jarak rata-rata ke klaster sendiri (a) : 3.229
jarak rata-rata ke klaster 1: 3.361
jarak rata-rata ke klaster 2: 7.622
klaster lain terdekat (b) : 3.361
s = (b - a) / max(a, b) = (3.361 - 3.229) / 3.361 = 0.039
silhouette_samples[57] dari sklearn : 0.039
Titik kontras (indeks 95, klaster 1): a = 1.263, b = 6.053
s manual = 0.791 | silhouette_samples[95] = 0.791
Rata-rata silhouette seluruh titik : 0.676
silhouette_score (API) : 0.676Mari kita bedah hasil eksekusinya:
- Baris 15–25 (sweep dua metrik): kolom inertia turun monoton 815,524 → 145,861 tanpa pernah naik; kolom silhouette memuncak 0,676 di
k=3lalu anjlok ke 0,430. Inertia tidak bisa berhenti dik=3atas kemampuannya sendiri — silhouette bisa, karena membelah gumpalan utuh menghasilkan titik perbatasan yang menarik rata-rata ke bawah. - Baris 32–44 (hitungan tangan untuk titik 57): titik ini adalah yang paling ragu-ragu di seluruh data. Jarak rata-ratanya ke klaster sendiri
a = 3,229— nyaris sama dengan jarak ke klaster tetanggab = 3,361. Masukkan ke rumus:s = (3,361 - 3,229) / 3,361 = 0,039, hampir nol — sang pelanggan memang berdiri di perbatasan dua segmen. Nilaisilhouette_samplesdari sklearn: 0,039, persis hitungan tangan kita. Perhatikan baris 35: jarak ke klaster sendiri dihitung dengan pembagi jumlah anggota dikurangi satu, karena jarak titik ke dirinya sendiri (nol) tidak diikutkan. - Baris 47–54 (titik kontras 95): kebalikannya,
a = 1,263jauh lebih kecil daripadab = 6,053, sehinggas = 0,791— pelanggan yang duduk nyaman jauh di dalam klasternya. Sekali lagi hasil manual dan API identik. - Baris 57–58 (rata-rata):
silhouette_scoreternyata bukan perhitungan tersembunyi — ia tepat rata-rata darisilhouette_samples, 0,676 di kedua baris.
Empat chapter clustering kita tuntas: konsep, tiga algoritme, dan kini alat ukurnya. Section berikutnya berganti fokus dari mengelompokkan menjadi meringkas — bagaimana memampatkan puluhan fitur yang saling berkorelasi menjadi beberapa sumbu baru tanpa kehilangan banyak informasi. Mesin pertamanya adalah Reduksi Dimensi dengan PCA.
Referensi Resmi #
- sklearn.metrics.silhouette_score — API Reference — definisi resmi skor rata-rata dan parameternya.
- sklearn.metrics.silhouette_samples — API Reference — skor per titik yang kita verifikasi secara manual.
- Clustering — scikit-learn User Guide — konteks penggunaan metrik internal untuk evaluasi clustering.