Dua proyek sebelumnya sama-sama berpijak pada data Toko Kopi Kita, tetapi kali ini sasarannya berpindah dari transaksi ke manusia yang bertransaksi. Sejak akhir 2025 toko Bu Rina menjalankan program kartu member sederhana: pelanggan mendaftarkan nama, lalu cukup menyebutkannya saat membayar agar aplikasi kasir mencatat kunjungan dan belanjanya. Pada 17 Januari 2026 Bu Rina mengekspor ringkasan 30 hari terakhir (18 Desember 2025 s.d. 16 Januari 2026) dan mendapati 32 baris data member yang belum pernah ia olah. Ia datang membawa tiga ide program penjualan — dan bagian ini kita pastikan dulu masalah apa yang sesungguhnya mau dipecahkan, karena clustering yang dijalankan tanpa masalah yang jelas hanya menghasilkan kelompok yang rapi tetapi tidak bisa ditindaklanjuti.

Tiga Ide Program yang Menunggu Jawaban #

Ketiga ide Bu Rina lahir dari pengamatan sehari-hari di balik kasir, dan ketiganya mengandung pertanyaan yang belum terjawab:

Ide programPertanyaan yang menunggu jawaban
Kartu prabayar (deposit kunjungan)Pelanggan seperti apa yang nyaman menyimpan uang di toko?
Promo yang tepat sasaranPromo apa yang relevan untuk perilaku belanja yang mana?
Penawaran akhir pekanKelompok mana yang paling perlu didorong datang pada Sabtu-Minggu?

Jawaban yang paling mudah untuk ketiganya adalah “semua pelanggan” — tetapi justru di situlah masalahnya. Promo yang ditujukan kepada semua orang biasanya terasa relevan bagi tidak seorang pun: pelanggan yang datang setiap hari butuh hal yang berbeda dengan pelanggan yang datang empat kali sebulan. Sebelum memutuskan program apa pun, kita perlu tahu apakah pelanggan memang berbeda-beda secara teratur, dan kalau ya, bedanya seperti apa.

Jebakan Pelanggan Rata-Rata #

Langkah pertama yang masuk akal adalah merangkum seluruh pelanggan dalam beberapa angka rata-rata. Kita lakukan hitungan cepat dengan menyisihkan dulu salinan persis dan sel yang tidak terbaca sebagai angka — dua kejanggalan yang sudah terlihat sekilas pada ekspor dan akan kita bereskan resmi pada Bagian 3:

python
import pandas as pd

mentah = pd.read_csv("../data/pelanggan.csv")

# Hitung cepat: singkirkan dulu salinan persis dan sel kunjungan yang tidak
# terbaca sebagai angka. Pembersihan resmi baru dikerjakan pada Bagian 3.
siap = mentah.drop_duplicates()
siap = siap.assign(
    kunjungan_30hari=pd.to_numeric(siap["kunjungan_30hari"], errors="coerce")
).dropna(subset=["kunjungan_30hari"])

rata_kunjungan = siap["kunjungan_30hari"].mean()
rata_belanja = siap["belanja_ribu"].mean()

print(f"Pelanggan yang dirata-rata : {len(siap)}")
print(f"Rata-rata kunjungan        : {rata_kunjungan:.1f} kali per 30 hari")
print(f"Rata-rata total belanja    : Rp {rata_belanja * 1000:,.0f}".replace(",", "."))

Output yang Dihasilkan:

text
Pelanggan yang dirata-rata : 30
Rata-rata kunjungan        : 12.6 kali per 30 hari
Rata-rata total belanja    : Rp 464.667

Mari kita bedah hasilnya:

  1. drop_duplicates(): salinan persis dibuang lebih dulu supaya pelanggan yang kebetulan tercatat dua kali tidak dihitung dua kali dalam rata-rata.
  2. pd.to_numeric(..., errors="coerce"): sel yang tidak terbaca sebagai angka (ada satu, seperti akan kita lihat) berubah menjadi NaN, lalu dropna menyisihkan barisnya dari hitungan cepat ini.
  3. Hasilnya: 30 pelanggan, rata-rata 12,6 kunjungan dan total belanja Rp 464.667 — nyaris setengah juta rupiah per pelanggan per bulan. Angka yang sehat di permukaan.

Sekarang kita uji: kalau rata-rata itu benar-benar mewakili pelanggan, seharusnya banyak pelanggan yang menyerupainya. Kita hitung berapa pelanggan yang berbeda paling jauh 10% dari rata-rata pada kedua metrik sekaligus, lalu sandingkan tiga pelanggan dengan total belanja nyaris setara rata-rata:

python
import pandas as pd

mentah = pd.read_csv("../data/pelanggan.csv")
siap = mentah.drop_duplicates()
siap = siap.assign(
    kunjungan_30hari=pd.to_numeric(siap["kunjungan_30hari"], errors="coerce")
).dropna(subset=["kunjungan_30hari"]).reset_index(drop=True)
siap["kunjungan_30hari"] = siap["kunjungan_30hari"].astype(int)

# "Menyerupai rata-rata" = selisih paling jauh 10% pada kedua metrik.
kunjungan = siap["kunjungan_30hari"]
belanja = siap["belanja_ribu"]
mirip = siap[
    kunjungan.between(kunjungan.mean() * 0.9, kunjungan.mean() * 1.1)
    & belanja.between(belanja.mean() * 0.9, belanja.mean() * 1.1)
]
print(f"Pelanggan mirip rata-rata (selisih <= 10%): {len(mirip)} dari {len(siap)}")
print(mirip[["pelanggan", "kunjungan_30hari", "belanja_ribu"]].to_string(index=False))

pilihan = siap[
    siap["pelanggan"].isin(["Siti Rahmawati", "Bambang Sutrisno", "Nurul Aini"])
].copy()
pilihan["belanja_per_kunjungan"] = (
    pilihan["belanja_ribu"] / pilihan["kunjungan_30hari"]
)
print("\nTiga pelanggan dengan total belanja nyaris setara rata-rata:")
print(pilihan[["pelanggan", "kunjungan_30hari", "belanja_ribu",
               "belanja_per_kunjungan"]].to_string(index=False))

Output yang Dihasilkan:

text
Pelanggan mirip rata-rata (selisih <= 10%): 3 dari 30
      pelanggan  kunjungan_30hari  belanja_ribu
     Nurul Aini                12           420
    Tina Melati                13           494
Putri Anggraini                12           432

Tiga pelanggan dengan total belanja nyaris setara rata-rata:
       pelanggan  kunjungan_30hari  belanja_ribu  belanja_per_kunjungan
  Siti Rahmawati                25           500                   20.0
Bambang Sutrisno                 4           480                  120.0
      Nurul Aini                12           420                   35.0

Dua hal perlu digarisbawahi dari keluaran tersebut:

  1. Hanya 3 dari 30 pelanggan yang menyerupai rata-rata pada kedua metrik. Sisanya berada di luar zona nyaman itu — artinya “pelanggan rata-rata” adalah sosok yang hampir tidak menggambarkan siapa pun.
  2. Tiga pelanggan di tabel kedua menyimpan cerita yang bertolak belakang. Siti Rahmawati datang 25 kali dengan belanja kecil tiap kunjungan (20 ribu), Bambang Sutrisno datang 4 kali dengan belanja besar tiap kunjungan (120 ribu), dan Nurul Aini berada di tengah-tengah. Total bulanan ketiganya nyaris sama — bagi kasir ketiganya tampak sebagai “pelanggan Rp 400-500 ribu”, padahal cara mereka memakai toko jauh berbeda.

Clustering: Membiarkan Data Menemukan Kelompoknya #

Karena tidak ada label “segmen” yang ditentukan siapa pun di awal — tidak seperti proyek prediksi yang punya target — kita berada di wilayah unsupervised learning. Alih-alih memprediksi label yang sudah ada, kita membiarkan algoritma menemukan kelompok yang memang muncul dari data. Algoritme yang akan kita pakai adalah K-Means: ia memetakan tiap pelanggan sebagai titik pada bidang fitur (seberapa sering datang, dan seberapa besar belanja tiap kunjungan), lalu mencari sejumlah pusat kelompok yang paling mewakili sebaran titik-titik itu. Mekanisme lengkapnya sudah kita bahas pada chapter K-Means Clustering ; di proyek ini kita memakainya sebagai alat, bukan membedah ulang teorinya.

Keberhasilan proyek ini punya definisi yang konkret: pada akhir Bagian 8, harus ada segmen-segmen bernama yang masing-masing bisa dipasangkan dengan satu ide program Bu Rina beserta alasan angkanya. Kalau kelompok yang muncul tidak bisa dipasangkan dengan tindakan apa pun, segmentasi itu gagal — betapapun rapi kelompoknya.

Masalah sudah jelas, dan pertanyaannya kini menyangkut bahan bakunya: dari mana data member ini berasal, dan seperti apa isi di balik 32 barisnya? Kita periksa pada Bagian 2: Menyiapkan Data .