Bagian 2 menyerahkan ruang fitur yang adil: total_belanja_ribu sudah di-log1p, keempat fitur sudah terskala, dan bukti silhouette 0,5544 berbanding 0,2331 menegaskan bahwa KMeans kini membaca seluruh dimensi perilaku. Pertanyaan struktural menyusul: berapa jumlah klaster yang tepat? Terlalu sedikit, empat karakter pelanggan yang berbeda terlebur menjadi satu; terlalu banyak, segmen terbelah-belah tanpa makna pemasaran yang bisa ditindaklanjuti. Karena tidak ada label, jawaban tidak datang dari akurasi melainkan dari dua metrik internal — silhouette yang mengukur kualitas pemisahan antar klaster dan kurva inertia yang mencari siku — lalu dari uji keterbacaan: apakah tiap klaster bercerita sebagai satu segmen bisnis.

Dua Kacamata untuk Memilih Jumlah Klaster #

Silhouette menghitung, untuk setiap pelanggan, selisih antara kedekatan rata-rata ke klaster sendiri (kohesi) dan kedekatan rata-rata ke klaster tetangga terdekat (separasi), dinormalisasi ke rentang −1 sampai 1. Nilai mendekati 1 berarti pelanggan duduk nyaman di tengah klaster miliknya; nilai mendekati 0 berarti pelanggan berdiri di perbatasan dua klaster. Rata-rata seluruh pelanggan menjadi skor kualitas pemisahan yang bisa dibandingkan antar nilai k.

Inertia — jumlah jarak kuadrat seluruh pelanggan ke centroid klaster miliknya — selalu turun ketika k bertambah; menambah klaster tak pernah membuat inertia naik. Inertia tidak punya maksimum yang bisa dicari. Yang kita cari adalah elbow: titik k tempat tambahan satu klaster tidak lagi memangkas inertia secara berarti. Kedua kacamata saling melengkapi — elbow kadang kabur pada data bertumpang tindih, dan silhouette memberi angka tunggal yang bisa diurutkan.

Menjalankan Pemilihan k dan Menyusun Profil #

Skrip bagian ini mengulang pra-pemrosesan Bagian 2 tanpa perubahan, melatih KMeans untuk k=2 sampai k=8, memilih k terbaik menurut silhouette, lalu memetakan centroid balik ke satuan asli — rupiah, pesanan, dan hari — agar profil klaster dapat dibaca tim pemasaran. Label bisnis diberikan lewat ambang domain yang eksplisit: recency 30 hari dan frekuensi 8 pesanan.

python
# Clustering: pilih jumlah klaster dengan silhouette + elbow (inertia),
# latih KMeans final, lalu susun profil perilaku tiap klaster dalam
# satuan asli beserta label bisnis. Pra-pemrosesan identik dengan
# src/03_preprocessing.py: log1p pada total_belanja_ribu lalu
# StandardScaler pada keempat fitur.
# Jalankan dari root proyek: python3 src/04_clustering.py

import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.preprocessing import StandardScaler

df = pd.read_csv("data/pelanggan.csv")
FITUR = [
    "total_belanja_ribu",
    "frekuensi_pesanan",
    "hari_sejak_transaksi",
    "rata_belanja_ribu",
]

# ---------- Pra-pemrosesan (identik Bagian 2) ----------
X_log = df[FITUR].copy()
X_log["total_belanja_ribu"] = np.log1p(X_log["total_belanja_ribu"])
X = StandardScaler().fit_transform(X_log)

# ---------- Pemilihan k: silhouette dan inertia ----------
print("== KMeans k=2..8: silhouette dan inertia (ruang terskala) ==")
print(f"{'k':>2s} {'silhouette':>11s} {'inertia':>9s}")
sil_per_k = {}
inertia_per_k = {}
for k in range(2, 9):
    km = KMeans(n_clusters=k, random_state=42, n_init=10).fit(X)
    sil = silhouette_score(X, km.labels_)
    sil_per_k[k] = sil
    inertia_per_k[k] = km.inertia_
    print(f"{k:2d} {sil:11.4f} {km.inertia_:9.1f}")

k_terbaik = max(sil_per_k, key=lambda k: sil_per_k[k])
print(f"\nk terbaik menurut silhouette: {k_terbaik} "
      f"(silhouette {sil_per_k[k_terbaik]:.4f})")
print("penurunan inertia dari k=2 ke k=3 dan k=3 ke k=4 menunjukkan"
      " siku (elbow) yang lembut; silhouette menjadi penentu.")

# ---------- Model final ----------
km_final = KMeans(n_clusters=k_terbaik, random_state=42, n_init=10).fit(X)
df_klaster = df.copy()
df_klaster["klaster"] = km_final.labels_

# ---------- Profil klaster dalam satuan asli ----------
profil = (
    df_klaster.groupby("klaster")
    .agg(
        n=("klaster", "size"),
        total=("total_belanja_ribu", "mean"),
        frek=("frekuensi_pesanan", "mean"),
        hari=("hari_sejak_transaksi", "mean"),
        rata=("rata_belanja_ribu", "mean"),
        pendapatan=("total_belanja_ribu", "sum"),
    )
    .sort_values("total", ascending=False)
)
pangsa_pendapatan = profil["pendapatan"] / profil["pendapatan"].sum()
pangsa_pelanggan = profil["n"] / profil["n"].sum()

def label_bisnis(baris):
    """Label bisnis dari statistik klaster: recency 30 hari,
    frekuensi 8 pesanan sebagai ambang heuristik domain."""
    if baris["hari"] < 30 and baris["frek"] >= 8:
        return "Pelanggan Setia"
    if baris["hari"] < 30 and baris["frek"] < 8:
        return "Pelanggan Baru"
    if baris["hari"] >= 30 and baris["frek"] >= 8:
        return "Pelanggan Mengendur"
    return "Pembeli Hemat"

print(f"\n== Profil klaster k={k_terbaik} (satuan asli, diurut total) ==")
print(
    f"{'klaster':>7s} {'n':>4s} {'total':>9s} {'frek':>6s}"
    f" {'hari':>6s} {'rata':>7s} {'%n':>6s} {'%pendapatan':>12s}  label"
)
for id_k, baris in profil.iterrows():
    print(
        f"{id_k:7d} {int(baris['n']):4d} {baris['total']:9.1f}"
        f" {baris['frek']:6.1f} {baris['hari']:6.1f} {baris['rata']:7.1f}"
        f" {pangsa_pelanggan[id_k]:6.1%} {pangsa_pendapatan[id_k]:12.1%}"
        f"  {label_bisnis(baris)}"
    )

# ---------- Kualitas pemisahan final ----------
sil_final = silhouette_score(X, km_final.labels_)
print(f"\nsilhouette model final : {sil_final:.4f}")
print(
    "jumlah iterasi KMeans terakhir :",
    km_final.n_iter_,
)
print(
    "jumlah pelanggan per klaster   :",
    ", ".join(
        f"klaster {id_k}={int(baris['n'])}"
        for id_k, baris in profil.iterrows()
    ),
)

Output yang Dihasilkan:

text
== KMeans k=2..8: silhouette dan inertia (ruang terskala) ==
 k  silhouette   inertia
 2      0.4247     679.7
 3      0.5218     343.1
 4      0.5544     206.9
 5      0.5353     156.8
 6      0.4877     135.0
 7      0.4138     120.2
 8      0.3701     108.4

k terbaik menurut silhouette: 4 (silhouette 0.5544)
penurunan inertia dari k=2 ke k=3 dan k=3 ke k=4 menunjukkan siku (elbow) yang lembut; silhouette menjadi penentu.

== Profil klaster k=4 (satuan asli, diurut total) ==
klaster    n     total   frek   hari    rata     %n  %pendapatan  label
      3   78   13525.1   32.2    8.8   421.4  26.0%        69.1%  Pelanggan Setia
      1   60    5103.0   14.2   58.0   362.2  20.0%        20.0%  Pelanggan Mengendur
      0   82    1241.1    2.5    5.5   492.6  27.3%         6.7%  Pelanggan Baru
      2   80     807.4    3.4   81.1   241.2  26.7%         4.2%  Pembeli Hemat

silhouette model final : 0.5544
jumlah iterasi KMeans terakhir : 4
jumlah pelanggan per klaster   : klaster 3=78, klaster 1=60, klaster 0=82, klaster 2=80

Mari kita bedah skrip clustering baris-per-baris:

  1. Baris 22–25 (pra-pemrosesan ulang): transformasi log1p dan StandardScaler diterapkan identik dengan Bagian 2 — skrip berdiri sendiri dan bisa dijalankan ulang tanpa keadaan tersimpan, selaras dengan aturan determinisme proyek.
  2. Baris 32–37 (loop k=2..8): untuk tiap k, KMeans dilatih dengan random_state=42, n_init=10 — sepuluh inisialisasi centroid berbeda dari generator berseed sama, dan KMeans menyimpan hasil ber-inertia terkecil. Inisialisasi k-means++ lalu sepuluh kali ulang membuat hasil stabil terhadap titik awal yang sial.
  3. Baris 39–43 (pemilihan k): max atas kamus silhouette memilih k dengan skor tertinggi. Baris 42–43 mencatat bahwa siku inertia bersifat lembut, sehingga silhouette menjadi penentu — pernyataan jujur yang sesuai output.
  4. Baris 51–62 (profil teragregasi): groupby("klaster") menghitung ukuran, rata-rata tiap fitur, dan total pendapatan per klaster, lalu diurutkan menurun menurut total belanja sehingga tabel profil terbaca dari segmen teratas.
  5. Baris 66–75 (label_bisnis): dua ambang domain — recency 30 hari (kira-kira satu siklus bulanan) dan frekuensi 8 pesanan — memetakan empat kombinasi menjadi empat nama segmen. Ambang sengaja ditulis eksplisit agar tim bisnis bisa menyanggahnya, bukan kotak gelap.
  6. Baris 63–64 (pangsa): pendapatan dan jumlah pelanggan tiap klaster dibagi totalnya, menghasilkan dua kolom yang menjadi dasar prioritas aksi pada Bagian 4.
  7. Baris 91–96 (diagnostik): silhouette model final dan jumlah iterasi konvergensi KMeans dicetak sebagai bukti kualitas dan kecepatan konvergensi.

Membaca Hasil: Empat Segmen yang Terpisah Jelas #

Tiga kandidat teratas layak diperbandingkan sebelum menerima pemenang:

KandidatSilhouetteInertiaAkibat pada segmen
k=30,5218343,1Pembeli Hemat dan Pelanggan Mengendur terlebur jadi satu klaster 126 pelanggan
k=40,5544206,9Empat segmen berbeda pada keempat dimensi RFM
k=50,5353156,8Klaster pelanggan kecil-aktif terbelah dua tanpa perbedaan strategi yang jelas

Silhouette memuncak di k=4 dengan 0,5544 dan turun lagi di k=5; inertia turun tajam dari 679,7 ke 343,1 ke 206,9, lalu penurunan menyusut tiga kali lipat setelahnya — siku yang lembut namun konsisten dengan silhouette. Penting untuk membaca juga kegagalan kandidat: pada k=3, klaster berisi 126 pelanggan mencampur pembeli hemat (rata belanja kecil, 3 pesanan) dengan pelanggan mengendur (14 pesanan) — dua populasi yang menuntut strategi berbeda. Pada k=5, klaster pelanggan kecil-aktif pecah menjadi dua kelompok ber-recency serupa (8,3 dan 5,6 hari) yang hanya terbedakan nilai pesanan rata-rata — pembedaan tanpa implikasi aksi.

Profil k=4 menghasilkan empat segmen dengan wajah bisnis yang tegas. Pelanggan Setia: 78 pelanggan (26,0%), total rata-rata Rp13.525.000, 32,2 pesanan, transaksi terakhir 9 hari lalu — 69,1% pendapatan toko. Pelanggan Mengendur: 60 pelanggan dengan frekuensi 14,2 namun recency 58 hari — sinyal dulu aktif, kini menjauh. Pelanggan Baru: 82 pelanggan dengan recency 5,5 hari dan frekuensi 2,5 — segar namun belum teruji. Pembeli Hemat: 80 pelanggan dengan rata belanja Rp241.000 dan recency 81 hari — hadir hanya saat ada insentif. Menariknya, komposisi ini hampir persis mengikuti kelompok laten yang ditanam pembangkit data pada Bagian 1 (78/61/84/77) — clustering berhasil menemukan kembali struktur yang tidak pernah dilihatnya.

Empat segmen kini bernama dan profilnya terkuantifikasi, termasuk pangsa pendapatan masing-masing. Pekerjaan yang tersisa adalah pekerjaan yang paling menentukan nilai proyek: menerjemahkan setiap profil menjadi aksi pemasaran konkret dengan urutan prioritas, lalu merangkai seluruh temuan menjadi laporan yang bisa dibaca pemangku kepentingan. Semua itu dikerjakan pada Bagian 4: Rekomendasi Aksi.

Referensi Resmi #