Clustering buatan tangan pada chapter sebelumnya bekerja, tetapi tiga keputusannya masih kita pegang: memilih jangkar, menetapkan jumlahnya, dan membiarkan posisinya diam. K-Means adalah algoritma yang mengambil alih keputusan ketiga — jangkarnya (kini bernama centroid) digeser terus-menerus ke rata-rata anggotanya sampai berhenti bergerak. Algoritma ini menjadi clustering paling populer karena cepat, mudah dipahami, dan cukup baik untuk kebanyakan data berbentuk gumpalan. Pada chapter ini kita jalankan dulu algoritmanya secara manual dua putaran, lalu memakainya untuk men-segment 20 pelanggan Toko Kopi Kita lewat scikit-learn, dan menutup dengan kelemahan-kelemahan yang harus kita ketahui sebelum memakainya sungguhan.

Algoritma Empat Langkah #

K-Means menjawab satu pertanyaan berulang: kalau pusat kelompok ada di sini, siapa saja anggotanya? Empat langkahnya:

  1. Pilih k centroid awal — k adalah jumlah kelompok yang kita tetapkan di awal; posisi awal centroid bisa acak atau dengan strategi k-means++.
  2. Tugaskan setiap titik ke centroid terdekat menurut jarak (biasanya Euclidean pada data yang sudah distandarisasi).
  3. Geser setiap centroid ke rata-rata seluruh titik yang menempel padanya.
  4. Ulangi langkah 2-3 sampai tidak ada titik yang berpindah kelompok dan centroid berhenti bergerak — disebut konvergen.

Langkah 2 dan 3 saling menopang seperti ayam dan telur: keanggotaan menentukan posisi centroid, posisi centroid menentukan keanggotaan. Iterasi inilah yang membuat K-Means “belajar” dari data — bukan dari label, melainkan dari konvergensi kedua langkah tersebut.

K-Means Manual: Dua Putaran Pertama #

Kita ambil enam pelanggan dari chapter sebelumnya dan jalankan mesinnya tanpa library, dua centroid dengan tebakan awal yang sengaja kurang tepat:

python
import numpy as np

# Enam pelanggan: (belanja ribu Rp/bulan, kunjungan/bulan)
titik = np.array([
    [40, 2], [55, 1], [45, 4],        # pengunjung sesekali
    [150, 11], [160, 12], [140, 10],  # pengunjung rutin
])

# Tebakan awal posisi centroid (dua titik di sekitar masing-masing gumpalan)
centroid = np.array([[42.0, 3.0], [145.0, 9.0]])

for putaran in range(1, 4):
    # Langkah 2: tiap titik bergabung ke centroid terdekat
    jarak = np.linalg.norm(titik[:, None, :] - centroid[None, :, :], axis=2)
    label = jarak.argmin(axis=1)

    # Langkah 3: centroid pindah ke rata-rata anggotanya
    baru = np.array([titik[label == c].mean(axis=0) for c in range(len(centroid))])

    print("Putaran %d: label = %s" % (putaran, label.tolist()))
    print("  centroid baru = %s" % np.round(baru, 2).tolist())

    if np.abs(centroid - baru).max() < 1e-9:
        print("  centroid tidak berubah -> algoritma berhenti")
        break
    centroid = baru

Output yang Dihasilkan:

text
Putaran 1: label = [0, 0, 0, 1, 1, 1]
  centroid baru = [[46.67, 2.33], [150.0, 11.0]]
Putaran 2: label = [0, 0, 0, 1, 1, 1]
  centroid baru = [[46.67, 2.33], [150.0, 11.0]]
  centroid tidak berubah -> algoritma berhenti

Mari kita telaah baris-baris kode di atas:

  1. titik sebagai array NumPy memungkinkan penghitungan jarak semua titik ke semua centroid sekaligus lewat broadcasting.
  2. titik[:, None, :] - centroid[None, :, :] menghasilkan tabel selisih berukuran 6×2×2 — tiap titik dikurangi tiap centroid; np.linalg.norm(..., axis=2) meringkasnya menjadi matriks jarak 6×2.
  3. jarak.argmin(axis=1) memilih indeks centroid terdekat bagi tiap titik — inilah langkah penugasan.
  4. titik[label == c].mean(axis=0) menghitung rata-rata anggota klaster c per fitur — inilah langkah penggeseran centroid.
  5. np.abs(centroid - baru).max() < 1e-9 mendeteksi konvergensi: bila pergeseran terbesar nyaris nol, iterasi dihentikan.

Pada putaran pertama label langsung benar (tiga titik kiri ke centroid 0, tiga titik kanan ke centroid 1), centroid bergeser ke pusat gumpalannya masing-masing, dan putaran kedua tidak mengubah apa pun — konvergen. Pada data nyata, konvergensi jarang sesegera ini; penugasan awal yang kurang beruntung bisa membutuhkan banyak putaran atau berhenti di pembagian yang buruk. Masalah itu kita tangani lewat parameter n_init sebentar lagi.

K-Means scikit-learn pada 20 Pelanggan #

Sekarang skala yang lebih realistis: 20 pelanggan dengan dua fitur yang sama — belanja (ribu rupiah per bulan) dan frekuensi (kunjungan per bulan). Kita standarisasi dulu dengan StandardScaler (ingat pelajaran chapter sebelumnya: skala menentukan jarak), lalu memanggil KMeans:

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

# 20 pelanggan Toko Kopi Kita: belanja (ribu Rp/bulan), frekuensi (kali/bulan)
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],
})

fitur = ["belanja", "frekuensi"]
skala = StandardScaler()
fitur_z = skala.fit_transform(pelanggan[fitur])

model = KMeans(n_clusters=3, random_state=42, n_init="auto")
label = model.fit_predict(fitur_z)

pelanggan["cluster"] = label
profil = pelanggan.groupby("cluster").agg(
    jumlah=("belanja", "size"),
    belanja_rata2=("belanja", "mean"),
    frekuensi_rata2=("frekuensi", "mean"),
).round(1)
print(profil)
print()

pusat_asli = skala.inverse_transform(model.cluster_centers_)
for c, (b, f) in enumerate(pusat_asli):
    print("centroid cluster %d: belanja %.1f, frekuensi %.1f" % (c, b, f))
print()
print("inertia (pada data z-skor): %.2f" % model.inertia_)
print()
print(pelanggan.sort_values(["cluster", "belanja"]).to_string(index=False))

Output yang Dihasilkan:

text
         jumlah  belanja_rata2  frekuensi_rata2
cluster                                        
0             6          160.0             10.0
1             8          385.0              6.6
2             6           47.5              2.3

centroid cluster 0: belanja 160.0, frekuensi 10.0
centroid cluster 1: belanja 385.0, frekuensi 6.6
centroid cluster 2: belanja 47.5, frekuensi 2.3

inertia (pada data z-skor): 3.09

 belanja  frekuensi  cluster
     140         10        0
     150         11        0
     160         12        0
     165         10        0
     170          9        0
     175          8        0
     350          7        1
     360          6        1
     370          5        1
     380          6        1
     390          9        1
     400          5        1
     410          7        1
     420          8        1
      35          3        2
      40          2        2
      45          4        2
      50          2        2
      55          1        2
      60          2        2

Bedah kode dan hasilnya:

  1. StandardScaler().fit_transform mengubah kedua fitur ke z-skor sebelum jarak dihitung; tanpa langkah ini, kolom belanja (rentang 35-420) akan menenggelamkan kolom frekuensi (rentang 1-12) persis seperti simulasi chapter sebelumnya.
  2. KMeans(n_clusters=3, random_state=42, n_init="auto") memuat tiga pengaturan penting sekaligus — lihat tabel di bawah.
  3. fit_predict menjalankan pelatihan lalu mengembalikan label klaster setiap baris — gabungan fit() + predict() dalam satu panggilan.
  4. groupby("cluster").agg(...) memrofil hasilnya: jumlah anggota dan rata-rata tiap fitur. Tiga kelompok terbaca jelas — pelanggan hemat sesekali (belanja 47,5; frekuensi 2,3), pelanggan rutin hemat (160; 10,0), dan pelanggan rutin dengan belanja besar (385; 6,6).
  5. skala.inverse_transform(model.cluster_centers_) menerjemahkan posisi centroid dari ruang z-skor kembali ke rupiah dan kunjungan, sehingga bisa dibaca tim pemasaran.
  6. model.inertia_ adalah jumlah kuadrat jarak semua titik ke centroid klasternya — angka 3,09 pada data z-skor; makin kecil makin rapat klaster. Ukuran ini menjadi bahan elbow method di chapter Evaluasi Clustering .

Tiga parameter yang wajib kita pahami:

ParameterPeranNilai pada contoh
n_clustersjumlah kelompok k — ditetapkan di awal3
random_stateseed untuk posisi awal centroid, agar hasil bisa direproduksi42
n_initberapa kali algoritma diulang dari posisi awal berbeda; hasil terbaik yang dipakai"auto"

Kelemahan yang Perlu Kita Waspadai #

K-Means punya tiga asumsi yang tidak selalu berlaku pada data nyata:

Asumsi K-MeansAkibat bila dilanggarPenangkalnya
Klaster berbentuk gumpalan bulat yang sama ukurannyaklaster memanjang/lengkung terbelah paksaDBSCAN (chapter Density-Based Clustering )
Jumlah k sudah diketahui di awalk yang salah menghasilkan potongan yang menyesatkanelbow method & silhouette (chapter Evaluasi Clustering )
Setiap titik wajib masuk suatu klasterpencilan menarik centroid dan merusak pembagianDBSCAN menandai pencilan sebagai noise

Tidak ada satupun algoritma clustering yang unggul untuk semua bentuk data — itulah mengapa Section ini membawa beberapa keluarga sekaligus. Kalau asumsi bulat terpenuhi dan k bisa diperkirakan, K-Means sulit ditanding kecepatannya; kalau bentuk datamu aneh-aneh, keluarga lain menunggu di chapter berikutnya.

Rangkuman #

  • K-Means berputar pada empat langkah: pilih k centroid, tugaskan titik ke centroid terdekat, geser centroid ke rata-rata anggota, ulang sampai konvergen.
  • Versi manual memperlihatkan mekanismenya: setelah penugasan, centroid bergerak ke pusat gumpalan dan iterasi berikutnya tidak mengubah apa pun.
  • Pada scikit-learn, standarisasi dikerjakan lebih dulu, lalu KMeans(n_clusters=3, random_state=42, n_init="auto"); fit_predict menghasilkan label, cluster_centers_ posisi centroid, inertia_ ukuran kerapatan.
  • Nomor klaster hanyalah penanda tanpa urutan; kelemahan utama K-Means adalah asumsi bentuk bulat, keharusan menetapkan k di awal, dan kewajiban memasukkan semua titik.

Salah satu kelemahan itu — keharusan memilih k sebelum melihat hasilnya — dijawab oleh keluarga clustering berikutnya: hierarchical clustering membangun seluruh pohon pengelompokan lebih dulu, dan jumlah kelompok tinggal dipilih belakangan dengan memotong pohonnya. Lanjut ke chapter Hierarchical Clustering .

Referensi #

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