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.
# 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:
== 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=80Mari kita bedah skrip clustering baris-per-baris:
- Baris 22–25 (pra-pemrosesan ulang): transformasi
log1pdanStandardScalerditerapkan identik dengan Bagian 2 — skrip berdiri sendiri dan bisa dijalankan ulang tanpa keadaan tersimpan, selaras dengan aturan determinisme proyek. - 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. - Baris 39–43 (pemilihan k):
maxatas 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. - 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. - 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. - Baris 63–64 (pangsa): pendapatan dan jumlah pelanggan tiap klaster dibagi totalnya, menghasilkan dua kolom yang menjadi dasar prioritas aksi pada Bagian 4.
- 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:
| Kandidat | Silhouette | Inertia | Akibat pada segmen |
|---|---|---|---|
| k=3 | 0,5218 | 343,1 | Pembeli Hemat dan Pelanggan Mengendur terlebur jadi satu klaster 126 pelanggan |
| k=4 | 0,5544 | 206,9 | Empat segmen berbeda pada keempat dimensi RFM |
| k=5 | 0,5353 | 156,8 | Klaster 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 #
- Clustering — scikit-learn User Guide — KMeans, inertia, dan panduan memilih jumlah klaster.
- sklearn.cluster.KMeans — API
—
n_init,n_iter_, daninertia_. - sklearn.metrics.silhouette_score — API — perhitungan koefisien silhouette.