K-Means pada Bagian 6 menghasilkan tiga kelompok yang terlihat meyakinkan — tetapi tampilan meyakinkan justru bahaya terbesar clustering, karena algoritme ini tidak pernah menolak mengelompokkan data apa pun, sekalipun k-nya keliru. Pada bagian ini kita mengadili pilihan k dengan dua alat ukur baku: skor silhouette yang menilai seberapa rapat anggota di dalam kelompoknya dan seberapa terpisah kelompok satu dengan lainnya, serta inertia yang justru perlu dibaca dengan hati-hati karena sifatnya selalu menurun. Kita jalankan keduanya untuk k=2 sampai k=6, lalu memutuskan dengan angka di tangan.
Dua Alat Ukur dan Batasannya #
Skor silhouette menghitung, untuk setiap pelanggan, perbandingan antara jarak rata-rata ke tetangga sekampung (seberapa rapat kelompoknya) dan jarak rata-rata ke kelompok lain terdekat (seberapa jauh ia dari yang bukan kelompoknya). Hasilnya dirangkum satu angka antara -1 sampai 1: mendekati 1 berarti pelanggan nyaman di kelompoknya, mendekati 0 berarti ia berdiri di perbatasan, dan negatif berarti ia kemungkinan masuk kelompok yang salah. Sebagai pegangan praktis, skor di atas 0,5 menandakan struktur kelompok yang kuat.
Inertia menjumlahkan kuadrat jarak seluruh pelanggan ke pusat clusternya — makin kecil makin rapat kelompok-kelompoknya. Alat ini ampuh tetapi punya kelemahan yang harus dipahami sejak awal:
Menjalankan k=2 sampai k=6 #
Kedua ukuran dihitung untuk lima nilai k dengan alur yang sama persis — resep bersih, dua fitur, StandardScaler — sehingga satu-satunya yang berubah hanyalah jumlah cluster:
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.preprocessing import StandardScaler
def muat_bersih(path):
"""Muat data mentah pelanggan dan terapkan seluruh langkah pembersihan."""
df = pd.read_csv(path)
df = df.drop_duplicates().reset_index(drop=True)
df["kunjungan_30hari"] = pd.to_numeric(df["kunjungan_30hari"], errors="coerce")
df = df.dropna(subset=["kunjungan_30hari"]).reset_index(drop=True)
df["kota"] = df["kota"].str.strip().str.title()
df["kunjungan_30hari"] = df["kunjungan_30hari"].astype(int)
return df
bersih = muat_bersih("../data/pelanggan.csv")
bersih["belanja_per_kunjungan"] = (
bersih["belanja_ribu"] / bersih["kunjungan_30hari"]
)
fitur = bersih[["kunjungan_30hari", "belanja_per_kunjungan"]]
skala = StandardScaler().fit_transform(fitur)
print(f"{'k':>3} {'inertia':>9} {'silhouette':>11}")
for k in range(2, 7):
km = KMeans(n_clusters=k, random_state=42, n_init="auto").fit(skala)
skor = silhouette_score(skala, km.labels_)
print(f"{k:>3} {km.inertia_:>9.3f} {skor:>11.3f}")Output yang Dihasilkan:
k inertia silhouette
2 22.820 0.495
3 5.167 0.687
4 3.103 0.644
5 2.427 0.553
6 1.561 0.511Mari kita baca tabel tersebut dengan kedua alat sekaligus:
- Silhouette memuncak pada k=3: 0,687 — naik tajam dari 0,495 pada
k=2, lalu menurun terus untukk=4ke atas. Dengan ambang praktis 0,5, hanyak=3dank=2yang berada di zona kuat, dank=3jauh lebih tinggi. - Inertia menurun tajam lalu melandai tepat setelah k=3: dari 22,820 ke 5,167 saat
kbergerak dari 2 ke 3 (turun 77%), kemudian 5,167 ke 3,103 (40%), dan makin lama makin kecil persentannya. Siku kurva ada dik=3. - Kedua alat sepakat — dan itu penting, karena keduanya menilai dari sudut berbeda: silhouette menilai kualitas pemisahan, elbow menilai kecukupan jumlah kelompok.
Membaca Siku dan Puncak Sekaligus #
Tabel angka dibaca berurutan; grafik membaca semuanya sekaligus. Kita gambar kurva inertia dan batang silhouette berdampingan, dengan k=3 diberi warna berbeda agar posisinya menonjol:
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.preprocessing import StandardScaler
def muat_bersih(path):
"""Muat data mentah pelanggan dan terapkan seluruh langkah pembersihan."""
df = pd.read_csv(path)
df = df.drop_duplicates().reset_index(drop=True)
df["kunjungan_30hari"] = pd.to_numeric(df["kunjungan_30hari"], errors="coerce")
df = df.dropna(subset=["kunjungan_30hari"]).reset_index(drop=True)
df["kota"] = df["kota"].str.strip().str.title()
df["kunjungan_30hari"] = df["kunjungan_30hari"].astype(int)
return df
bersih = muat_bersih("../data/pelanggan.csv")
bersih["belanja_per_kunjungan"] = (
bersih["belanja_ribu"] / bersih["kunjungan_30hari"]
)
fitur = bersih[["kunjungan_30hari", "belanja_per_kunjungan"]]
skala = StandardScaler().fit_transform(fitur)
daftar_k = list(range(2, 7))
inertia, silhouette = [], []
for k in daftar_k:
km = KMeans(n_clusters=k, random_state=42, n_init="auto").fit(skala)
inertia.append(km.inertia_)
silhouette.append(silhouette_score(skala, km.labels_))
warna = ["#b9b6b0" if k != 3 else "#2a6f4e" for k in daftar_k]
fig, (ax_siku, ax_sil) = plt.subplots(1, 2, figsize=(11, 4.5))
ax_siku.plot(daftar_k, inertia, marker="o", color="#6f4e37")
ax_siku.set_title("Elbow: inertia turun tajam lalu melandai")
ax_siku.set_xlabel("Jumlah cluster k")
ax_siku.set_ylabel("Inertia")
ax_siku.set_xticks(daftar_k)
ax_siku.grid(alpha=0.3)
batang = ax_sil.bar(daftar_k, silhouette, color=warna)
ax_sil.bar_label(batang, fmt="%.3f", padding=3)
ax_sil.set_title("Silhouette tertinggi pada k=3")
ax_sil.set_xlabel("Jumlah cluster k")
ax_sil.set_ylabel("Skor silhouette")
ax_sil.set_xticks(daftar_k)
ax_sil.set_ylim(0, 0.8)
fig.tight_layout()
plt.show()Panel kiri memperlihatkan kurva cokelat yang menurun curam dari k=2 ke k=3, lalu melandai dan berbaris hampir datar — bentuk siku klasik yang menandakan penambahan cluster setelah titik itu tidak lagi membeli kerapatan berarti. Panel kanan menampilkan lima batang dengan label nilainya: batang hijau k=3 paling menjulang (0,687), disusul k=4 (0,644), sementara tiga batang lainnya tertinggal di bawah 0,56. Grafik ini layak dilampirkan pada laporan karena membuat alasan memilih k=3 terlihat tanpa perlu membaca tabel.
Dengan dua alat ukur yang sepakat, keputusannya tercatat: k=3. Ada pula alasan ketiga yang tidak kalah sah — kecocokan dengan masalah bisnis. Bu Rina membawa tiga ide program, dan tiga segmen memungkinkan tiap segmen dipasangkan satu program; empat atau lima segmen akan memaksa dua program berbagi satu segmen atau menciptakan segmen tanpa program. Alasan bisnis tidak pernah menggantikan angka, tetapi bila angka mendukungnya, keputusan menjadi dua kali lebih kokoh.
Verdik sudah jatuh dan tiga kelompok resmi menjadi tiga segmen. Namun angka 0, 1, 2 belum berbicara apa-apa kepada Bu Rina — kita isi dengan wajah dan nama pada Bagian 8: Menginterpretasikan Segmen .