Proyek ketiga Course ini membawa kita ke ranah unsupervised learning: membagi pelanggan menjadi segmen yang bermakna berdasarkan perilaku belanja, tanpa label yang ditentukan siapa pun di awal. Kita menyiapkan fitur dari data transaksi member, membersihkannya, membuat fitur agregat, menerapkan K-Means, mengevaluasi jumlah cluster, menginterpretasikan tiap segmen, lalu menutupnya dengan laporan yang dapat ditindaklanjuti. Panggungnya masih dunia yang sama dengan dua proyek sebelumnya — Toko Kopi Kita milik Bu Rina di Bandung — dan seluruh kerjanya dirangkai dalam sembilan bagian berurutan. Setiap angka pada laporan akhir dihitung oleh pipeline yang sama, sehingga hasilnya dapat diulang oleh siapa pun.
Cerita: Program Member Toko Kopi Kita #
Sejak akhir tahun 2025, Toko Kopi Kita menjalankan program kartu member sederhana: pelanggan mendaftarkan nama, lalu cukup menyebutkannya saat membayar agar aplikasi kasir mencatat kunjungan dan belanjanya. Bu Rina belum sempat mengolah data itu — sampai pengalaman proyek analisis penjualan membuatnya percaya bahwa keputusan lebih sehat bila berlandaskan angka. Pada 17 Januari 2026, ia mengekspor ringkasan 30 hari terakhir (18 Desember 2025 s.d. 16 Januari 2026) untuk seluruh pelanggan member yang aktif: 32 baris data.
Bu Rina punya tiga ide program penjualan, tetapi ketiganya masih samar karena ia tidak tahu kepada siapa masing-masing cocok:
| Ide program | Pertanyaan yang belum terjawab |
|---|---|
| Kartu prabayar (deposit kunjungan) | Pelanggan seperti apa yang akan tertarik menyimpan uang di toko? |
| Promo yang tepat sasaran | Promo apa yang relevan untuk perilaku belanja yang mana? |
| Penawaran akhir pekan | Segmen mana yang paling perlu didorong datang pada Sabtu-Minggu? |
Jawaban “semua pelanggan” justru masalahnya. Seperti akan kita lihat pada Bagian 1, rata-rata seluruh pelanggan — 12,6 kunjungan dan total belanja Rp 464.670 per 30 hari — hampir tidak menggambarkan siapa pun; total belanja seluruh segmen ternyata nyaris sama, padahal perilakunya sangat berbeda. Karena itu proyek ini memakai clustering: membiarkan data menunjukkan kelompok-kelompok pelanggan yang benar-benar ada, lalu menyandingkan tiap kelompok dengan program yang paling pas.
Isi Proyek Ini #
Proyek tersusun atas tiga berkas pendukung dan sembilan bagian pengerjaan:
| Berkas | Peran |
|---|---|
data/pelanggan.csv | Dataset mentah: ringkasan 30 hari untuk tiap pelanggan member |
src/segmentasi.py | Pipeline utuh dari muat data sampai penulisan laporan |
laporan/laporan-akhir.txt | Laporan akhir yang ditulis otomatis oleh pipeline |
part-1-… sampai part-9-… | Tahapan pengerjaan berurutan, satu topik per bagian |
Dataset mentah mengandung tiga masalah kecil yang sengaja kita biarkan apa adanya, lalu kita temukan dan perbaiki pada Bagian 3 — karena itulah cara kerja proyek data nyata. Untuk menjalankan pipeline di komputer Anda, unduh ketiga berkas pendukung dari halaman ini (tautan unduh ada pada kepala tiap blok kode di bawah), susun dengan struktur folder yang sama, lalu jalankan dari folder proyek:
python3 src/segmentasi.pyPipeline ini deterministik: tanpa akses jaringan, tanpa pembacaan jam sistem, dan selalu menghasilkan laporan yang identik dari data yang sama.
Dataset Pelanggan Member #
Berkas data/pelanggan.csv berbentuk panjang: satu baris per pelanggan member, dengan lima kolom.
| Kolom | Makna |
|---|---|
pelanggan | Nama pelanggan terdaftar pada program member |
kota | Kota tempat tinggal pelanggan |
kunjungan_30hari | Jumlah kunjungan selama 30 hari terakhir |
belanja_ribu | Total belanja 30 hari terakhir, dalam ribu rupiah |
menu_favorit | Menu minuman yang paling sering dibeli |
Berikut isi lengkapnya — perhatikan bahwa jumlah barisnya lebih banyak daripada jumlah pelanggan yang akan kita analisis, dan beberapa baris memuat kejanggalan kecil:
pelanggan,kota,kunjungan_30hari,belanja_ribu,menu_favorit
Siti Rahmawati,Bandung,25,500,americano
Bambang Sutrisno,Bandung,4,480,americano
Andi Firmansyah,Bandung,10,400,matcha latte
Budi Santoso,Bandung,22,462,kopi susu
Budi Santoso,Bandung,22,462,kopi susu
Nurul Aini,Bandung,12,420,teh tawar
Ratna Dewi,Bandung,5,700,kopi susu
Dewi Lestari,bandung,20,380,kopi susu
Dimas Prasetyo,Bandung,8,440,kopi susu
Agus Pratama,Cimahi,24,576,americano
Ayu Kusuma,Cimahi,11,495,matcha latte
Tono Hartono,Cimahi,3,330,americano
Rina Marlina,Bandung,19,437,kopi susu
Rizky Maulana,Bandung,9,450,americano
Sri Wahyuni,Bandung,6,540,teh tawar
Joko Susilo,Bandung,21,462,americano
Tina Melati,Bandung,13,494,teh tawar
Eko Nugroho,Bandung,4,600,kopi susu
Indah Permata,Bandung,23,414,kopi susu
Yusuf Hamdani,Bandung,10,420,matcha latte
Fitri Handayani,Bandung,5,500,matcha latte
Hendra Wijaya,Cimahi,18,450,americano
Laila Sari,Bandung,14,448,teh tawar
Rani Oktaviani,Bandung,-,320,kopi susu
Gunawan Saputra,Sumedang,3,390,americano
Bagus Setiawan,Sumedang,8,464,kopi susu
Maya Sari,Bandung,20,420,kopi susu
Putri Anggraini,Bandung,12,432,matcha latte
Lia Kartika,Bandung,6,480,teh tawar
Arif Budiman,Bandung,9,432,americano
Fajar Ramadhan,Bandung,22,440,kopi susu
Sari Wulandari,Bandung,11,484,teh tawarPipeline Segmentasi #
Seluruh analisis dirangkai dalam satu skrip, src/segmentasi.py. Skrip ini memuat data mentah, memeriksa masalahnya, membersihkannya, menurunkan fitur belanja_per_kunjungan (rata-rata belanja tiap kunjungan), menormalisasi kedua fitur dengan StandardScaler, mengelompokkan pelanggan dengan K-Means (k=3, random_state=42), mengevaluasi pilihan k lewat silhouette dan elbow, menyusun profil tiap cluster, lalu menulis laporan lengkap ke laporan/laporan-akhir.txt.
"""segmentasi.py: pipeline segmentasi pelanggan Toko Kopi Kita.
Pipeline ini menjalankan alur utuh satu proyek unsupervised learning:
muat data member mentah, periksa masalah, bersihkan, buat fitur
belanja_per_kunjungan, normalisasi dengan StandardScaler, kelompokkan
pelanggan dengan K-Means (k=3, random_state=42), evaluasi jumlah cluster
lewat silhouette dan elbow, susun profil tiap segmen, lalu tulis laporan
lengkap ke laporan/laporan-akhir.txt.
Cara menjalankan (dari folder proyek segmentasi-pelanggan):
python3 src/segmentasi.py
Pipeline ini deterministik: tidak membaca jam sistem, tidak mengakses
jaringan, dan selalu menghasilkan keluaran yang sama dari data yang sama.
"""
from pathlib import Path
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.preprocessing import StandardScaler
BERKAS_DATA = Path("data/pelanggan.csv")
BERKAS_LAPORAN = Path("laporan/laporan-akhir.txt")
PERIODE = "18 Desember 2025 s.d. 16 Januari 2026"
K_MIN = 2
K_MAKS = 6
K_TERPILIH = 3
RANDOM_STATE = 42
def rupiah(nilai_ribu: float) -> str:
"""Format nilai (dalam ribu rupiah) menjadi teks rupiah Indonesia."""
return "Rp " + f"{nilai_ribu * 1000:,.0f}".replace(",", ".")
def muat_data() -> pd.DataFrame:
"""Baca data mentah pelanggan member dari berkas CSV."""
df = pd.read_csv(BERKAS_DATA)
print(f"[1] Muat data mentah : {df.shape[0]} baris, {df.shape[1]} kolom")
return df
def periksa_masalah(df: pd.DataFrame) -> dict:
"""Kumpulkan temuan masalah pada data mentah sebelum dibersihkan."""
kota = df["kota"].str.strip()
tak_konsisten = sorted(set(kota) - set(kota.str.title()))
masalah = {
"duplikat": int(df.duplicated().sum()),
"kosong": int((df["kunjungan_30hari"] == "-").sum()),
"kota": tak_konsisten,
}
ringkas = [f"{masalah['duplikat']} baris duplikat persis"]
ringkas.append(f"{masalah['kosong']} sel kunjungan \"-\"")
if tak_konsisten:
ringkas.append("kota tak konsisten (" + ", ".join(tak_konsisten) + ")")
print("[2] Temuan validasi : " + "; ".join(ringkas))
return masalah
def bersihkan(df: pd.DataFrame) -> pd.DataFrame:
"""Buang duplikat, buang baris kunjungan "-", dan seragamkan kota."""
bersih = df.drop_duplicates().copy()
bersih["kunjungan_30hari"] = pd.to_numeric(bersih["kunjungan_30hari"], errors="coerce")
bersih = bersih.dropna(subset=["kunjungan_30hari"]).copy()
bersih["kota"] = bersih["kota"].str.strip().str.title()
bersih["kunjungan_30hari"] = bersih["kunjungan_30hari"].astype(int)
bersih = bersih.reset_index(drop=True)
print(
f"[3] Pembersihan : {len(bersih)} baris bersih"
" (duplikat dibuang, baris \"-\" dibuang, kota diseragamkan)"
)
return bersih
def buat_fitur(bersih: pd.DataFrame) -> pd.DataFrame:
"""Tambahkan fitur turunan belanja_per_kunjungan."""
fitur = bersih.copy()
fitur["belanja_per_kunjungan"] = fitur["belanja_ribu"] / fitur["kunjungan_30hari"]
kolom_fitur = fitur[["kunjungan_30hari", "belanja_per_kunjungan"]]
std_k = float(kolom_fitur["kunjungan_30hari"].std())
std_b = float(kolom_fitur["belanja_per_kunjungan"].std())
print(f"[4] Fitur clustering : kunjungan_30hari (std {std_k:.2f}),"
f" belanja_per_kunjungan (std {std_b:.2f})")
return fitur
def latih_kmeans(fitur: pd.DataFrame) -> tuple[pd.DataFrame, object]:
"""Normalisasi fitur, latih K-Means k=3, dan tempelkan label cluster."""
kolom_fitur = fitur[["kunjungan_30hari", "belanja_per_kunjungan"]]
skala = StandardScaler().fit_transform(kolom_fitur)
print("[5] StandardScaler : kedua fitur diskalakan ke mean 0, std 1")
model = KMeans(n_clusters=K_TERPILIH, random_state=RANDOM_STATE, n_init="auto")
label = model.fit_predict(skala)
berlabel = fitur.copy()
berlabel["cluster"] = label
jumlah = berlabel["cluster"].value_counts().sort_index()
rincian = ", ".join(f"cluster {c} = {n}" for c, n in jumlah.items())
print(f"[6] KMeans k=3 : {rincian} pelanggan")
return berlabel, skala
def evaluasi_k(skala) -> tuple[dict, dict]:
"""Hitung inertia dan silhouette untuk k=2..6, tampilkan sebagai tabel."""
inertia, silhouette = {}, {}
for k in range(K_MIN, K_MAKS + 1):
km = KMeans(n_clusters=k, random_state=RANDOM_STATE, n_init="auto").fit(skala)
inertia[k] = float(km.inertia_)
silhouette[k] = float(silhouette_score(skala, km.labels_))
teks_sil = "/".join(f"{silhouette[k]:.3f}" for k in range(K_MIN, K_MAKS + 1))
teks_int = "/".join(f"{inertia[k]:.3f}" for k in range(K_MIN, K_MAKS + 1))
print(f"[7] Evaluasi k={K_MIN}..{K_MAKS} : silhouette {teks_sil}")
print(f" (elbow inertia) : {teks_int}")
return inertia, silhouette
def profil_segmen(berlabel: pd.DataFrame) -> pd.DataFrame:
"""Agregatkan profil tiap cluster dan beri nama segmen."""
profil = berlabel.groupby("cluster").agg(
jumlah=("pelanggan", "count"),
kunjungan_rata=("kunjungan_30hari", "mean"),
kunjungan_min=("kunjungan_30hari", "min"),
kunjungan_maks=("kunjungan_30hari", "max"),
bpk_rata=("belanja_per_kunjungan", "mean"),
bpk_min=("belanja_per_kunjungan", "min"),
bpk_maks=("belanja_per_kunjungan", "max"),
total_rata=("belanja_ribu", "mean"),
)
def namai(baris) -> str:
if baris["kunjungan_rata"] >= 15:
return "Langganan Harian"
if baris["bpk_rata"] >= 70:
return "Pembeli Stok"
return "Pengunjung Sesekali"
profil["segmen"] = profil.apply(namai, axis=1)
rincian = ", ".join(f"{int(c)} = {s}" for c, s in profil["segmen"].items())
print(f"[8] Profil segmen : {rincian}")
return profil
def menu_teratas(daftar_menu: pd.Series) -> str:
"""Ringkas menu favorit satu segmen: jumlah menurun lalu nama abjad."""
hitung = daftar_menu.value_counts()
pasangan = sorted(hitung.items(), key=lambda x: (-x[1], x[0]))
return ", ".join(f"{menu} ({n})" for menu, n in pasangan)
def susun_laporan(
berlabel: pd.DataFrame,
profil: pd.DataFrame,
inertia: dict,
silhouette: dict,
) -> str:
"""Susun isi laporan akhir sebagai teks polos."""
total_pelanggan = int(profil["jumlah"].sum())
teks_sil = ", ".join(f"{silhouette[k]:.3f}" for k in range(K_MIN, K_MAKS + 1))
teks_int = ", ".join(f"{inertia[k]:.3f}" for k in range(K_MIN, K_MAKS + 1))
baris = []
baris += [
"=" * 68,
"LAPORAN AKHIR - SEGMENTASI PELANGGAN",
"Toko Kopi Kita, Bandung",
f"Periode data : 30 hari, {PERIODE}",
"=" * 68,
"",
"1. RINGKASAN DATA",
"-" * 68,
"Sumber : ekspor aplikasi kasir program kartu member",
"Data mentah : 32 baris, 5 kolom",
"Pembersihan : 1 baris duplikat persis dibuang (Budi Santoso",
" tercatat dua kali); 1 baris kunjungan \"-\"",
" dibuang (Rani Oktaviani, kartu baru); 1",
" penulisan kota diseragamkan (bandung -> Bandung)",
f"Data bersih : {total_pelanggan} pelanggan",
"Fitur : kunjungan_30hari dan belanja_per_kunjungan",
" (= belanja_ribu / kunjungan_30hari)",
"",
"2. METODE",
"-" * 68,
"Algoritma : K-Means, k=3, random_state=42, n_init=\"auto\"",
"Normalisasi : StandardScaler pada kedua fitur",
"Pemilihan k : silhouette k=2..6:",
" " + teks_sil + " (tertinggi pada k=3)",
" elbow inertia:",
" " + teks_int + " (turun tajam dari k=2",
" ke k=3, lalu melandai)",
f"Struktur : silhouette k=3 = {silhouette[3]:.3f}",
" (di atas 0.5 = struktur kelompok kuat)",
"",
"3. PROFIL SEGMEN",
"-" * 68,
]
for nomor, (c, p) in enumerate(profil.iterrows(), start=1):
anggota = berlabel[berlabel["cluster"] == c]
contoh = ", ".join(anggota["pelanggan"].head(3))
menu = menu_teratas(anggota["menu_favorit"])
persen = 100 * p["jumlah"] / total_pelanggan
baris += [
f"Segmen {nomor}: {p['segmen']} - {int(p['jumlah'])} pelanggan"
f" ({persen:.1f}%, cluster {int(c)})",
f" Kunjungan 30 hari : rata-rata {p['kunjungan_rata']:.1f}"
f" ({int(p['kunjungan_min'])}-{int(p['kunjungan_maks'])} kali)",
f" Belanja/kunjungan : rata-rata {rupiah(p['bpk_rata'])}"
f" ({rupiah(p['bpk_min'])} s.d. {rupiah(p['bpk_maks'])})",
f" Total 30 hari : rata-rata {rupiah(p['total_rata'])}",
f" Menu favorit : {menu}",
f" Contoh anggota : {contoh}",
"",
]
harian = profil[profil["segmen"] == "Langganan Harian"].iloc[0]
stok = profil[profil["segmen"] == "Pembeli Stok"].iloc[0]
sesekali = profil[profil["segmen"] == "Pengunjung Sesekali"].iloc[0]
baris += [
"4. TEMUAN UTAMA",
"-" * 68,
"T1 Total belanja 30 hari nyaris sama pada semua segmen",
f" ({rupiah(harian['total_rata'])} / {rupiah(stok['total_rata'])} /"
f" {rupiah(sesekali['total_rata'])} rata-rata).",
" Total belanja saja tidak cukup membedakan pelanggan; pola",
" yang membedakan ada pada kunjungan dan belanja per kunjungan.",
"T2 Ada dua cara menjadi pelanggan bernilai tinggi.",
f" Langganan Harian ({int(harian['jumlah'])} orang) datang hampir",
f" setiap hari (rata-rata {harian['kunjungan_rata']:.1f} kali)",
f" dengan belanja kecil ({rupiah(harian['bpk_rata'])}/kunjungan);",
f" Pembeli Stok ({int(stok['jumlah'])} orang) jarang datang",
f" (rata-rata {stok['kunjungan_rata']:.1f} kali) tetapi belanja",
f" besar tiap kunjungan ({rupiah(stok['bpk_rata'])}).",
"T3 Pengunjung Sesekali adalah segmen terbesar.",
f" {int(sesekali['jumlah'])} orang ({100 * sesekali['jumlah'] / total_pelanggan:.1f}%)",
f" dengan kunjungan menengah {int(sesekali['kunjungan_min'])}-"
f"{int(sesekali['kunjungan_maks'])} kali; menu favorit mereka",
" paling beragam (matcha latte dan teh tawar dominan).",
"T4 Menu favorit tidak memisahkan segmen.",
" Kopi susu dan americano muncul di semua segmen; angka",
" belanja per kunjungan yang membedakan perilaku pelanggan.",
"",
"5. REKOMENDASI PROGRAM",
"-" * 68,
"R1 Kartu prabayar untuk Langganan Harian.",
" Deposit (misal Rp 450.000 untuk 20 kali kunjungan) cocok",
" dengan rutinitas mereka dan mengunci kunjungan harian.",
"R2 Paket biji kopi untuk Pembeli Stok.",
" Belanja per kunjungan mereka jauh di atas segmen lain;",
" tawarkan paket 2x200 gram atau langganan biji bulanan.",
"R3 Penawaran akhir pekan untuk Pengunjung Sesekali.",
" Promo bundling Sabtu-Minggu dapat menaikkan frekuensi",
" kunjungan segmen terbesar ini; efektivitasnya perlu diuji",
" karena data kita belum mencatat hari kunjungan.",
"",
"6. KETERBATASAN",
"-" * 68,
"- Jendela data hanya 30 hari; pola musiman belum terlihat.",
"- Satu baris (Rani Oktaviani) dibuang karena jumlah kunjungan",
" tidak tercatat; segmen dapat bergeser bila datanya lengkap.",
"- 30 pelanggan adalah ukuran kecil; segmen beranggota 8 orang",
" perlu dibaca hati-hati sebelum digeneralisasi.",
"- menu_favorit hanya menu minuman; pembelian biji kopi pada",
" segmen Pembeli Stok adalah dugaan dari besaran belanja,",
" bukan dari catatan produk yang benar-benar dibeli.",
"",
"Diproses oleh : src/segmentasi.py (pipeline deterministik)",
"Data : data/pelanggan.csv",
]
return "\n".join(baris) + "\n"
def main() -> None:
"""Jalankan seluruh alur pipeline dan tulis laporan."""
mentah = muat_data()
periksa_masalah(mentah)
bersih = bersihkan(mentah)
fitur = buat_fitur(bersih)
berlabel, skala = latih_kmeans(fitur)
inertia, silhouette = evaluasi_k(skala)
profil = profil_segmen(berlabel)
laporan = susun_laporan(berlabel, profil, inertia, silhouette)
BERKAS_LAPORAN.parent.mkdir(parents=True, exist_ok=True)
BERKAS_LAPORAN.write_text(laporan, encoding="utf-8")
print(f"[9] Laporan ditulis ke : {BERKAS_LAPORAN}")
if __name__ == "__main__":
main()Output yang Dihasilkan:
[1] Muat data mentah : 32 baris, 5 kolom
[2] Temuan validasi : 1 baris duplikat persis; 1 sel kunjungan "-"; kota tak konsisten (bandung)
[3] Pembersihan : 30 baris bersih (duplikat dibuang, baris "-" dibuang, kota diseragamkan)
[4] Fitur clustering : kunjungan_30hari (std 7.05), belanja_per_kunjungan (std 40.12)
[5] StandardScaler : kedua fitur diskalakan ke mean 0, std 1
[6] KMeans k=3 : cluster 0 = 10, cluster 1 = 8, cluster 2 = 12 pelanggan
[7] Evaluasi k=2..6 : silhouette 0.495/0.687/0.644/0.553/0.511
(elbow inertia) : 22.820/5.167/3.103/2.427/1.561
[8] Profil segmen : 0 = Langganan Harian, 1 = Pembeli Stok, 2 = Pengunjung Sesekali
[9] Laporan ditulis ke : laporan/laporan-akhir.txtRingkasan tersebut adalah peta jalan seluruh proyek: langkah [1]–[3] kita kerjakan tuntas pada Bagian 2–3, langkah [4]–[5] pada Bagian 5 setelah eksplorasi data Bagian 4, langkah [6] pada Bagian 6, evaluasi pada langkah [7] dibedah pada Bagian 7, profil segmen pada langkah [8] menjadi jantung Bagian 8, dan laporan pada langkah [9] dibaca kritis pada Bagian 9. Mesin di balik langkah [6] — cara K-Means menggerakkan pusat cluster — sudah kita bahas pada chapter K-Means Clustering
, dan alasan fitur berskala berbeda perlu diseragamkan pada langkah [5] dibahas pada chapter Normalisasi
.
Laporan Akhir #
Pipeline menutup kerjanya dengan menulis laporan/laporan-akhir.txt — laporan teks polos berisi ringkasan data, metode, profil tiga segmen, temuan utama, rekomendasi program untuk Bu Rina, dan keterbatasan analisis. Seluruh angka pada laporan diambil dari perhitungan pipeline, bukan diketik manual.
====================================================================
LAPORAN AKHIR - SEGMENTASI PELANGGAN
Toko Kopi Kita, Bandung
Periode data : 30 hari, 18 Desember 2025 s.d. 16 Januari 2026
====================================================================
1. RINGKASAN DATA
--------------------------------------------------------------------
Sumber : ekspor aplikasi kasir program kartu member
Data mentah : 32 baris, 5 kolom
Pembersihan : 1 baris duplikat persis dibuang (Budi Santoso
tercatat dua kali); 1 baris kunjungan "-"
dibuang (Rani Oktaviani, kartu baru); 1
penulisan kota diseragamkan (bandung -> Bandung)
Data bersih : 30 pelanggan
Fitur : kunjungan_30hari dan belanja_per_kunjungan
(= belanja_ribu / kunjungan_30hari)
2. METODE
--------------------------------------------------------------------
Algoritma : K-Means, k=3, random_state=42, n_init="auto"
Normalisasi : StandardScaler pada kedua fitur
Pemilihan k : silhouette k=2..6:
0.495, 0.687, 0.644, 0.553, 0.511 (tertinggi pada k=3)
elbow inertia:
22.820, 5.167, 3.103, 2.427, 1.561 (turun tajam dari k=2
ke k=3, lalu melandai)
Struktur : silhouette k=3 = 0.687
(di atas 0.5 = struktur kelompok kuat)
3. PROFIL SEGMEN
--------------------------------------------------------------------
Segmen 1: Langganan Harian - 10 pelanggan (33.3%, cluster 0)
Kunjungan 30 hari : rata-rata 21.4 (18-25 kali)
Belanja/kunjungan : rata-rata Rp 21.300 (Rp 18.000 s.d. Rp 25.000)
Total 30 hari : rata-rata Rp 454.100
Menu favorit : kopi susu (6), americano (4)
Contoh anggota : Siti Rahmawati, Budi Santoso, Dewi Lestari
Segmen 2: Pembeli Stok - 8 pelanggan (26.7%, cluster 1)
Kunjungan 30 hari : rata-rata 4.5 (3-6 kali)
Belanja/kunjungan : rata-rata Rp 115.000 (Rp 80.000 s.d. Rp 150.000)
Total 30 hari : rata-rata Rp 502.500
Menu favorit : americano (3), kopi susu (2), teh tawar (2), matcha latte (1)
Contoh anggota : Bambang Sutrisno, Ratna Dewi, Tono Hartono
Segmen 3: Pengunjung Sesekali - 12 pelanggan (40.0%, cluster 2)
Kunjungan 30 hari : rata-rata 10.6 (8-14 kali)
Belanja/kunjungan : rata-rata Rp 43.583 (Rp 32.000 s.d. Rp 58.000)
Total 30 hari : rata-rata Rp 448.250
Menu favorit : matcha latte (4), teh tawar (4), americano (2), kopi susu (2)
Contoh anggota : Andi Firmansyah, Nurul Aini, Dimas Prasetyo
4. TEMUAN UTAMA
--------------------------------------------------------------------
T1 Total belanja 30 hari nyaris sama pada semua segmen
(Rp 454.100 / Rp 502.500 / Rp 448.250 rata-rata).
Total belanja saja tidak cukup membedakan pelanggan; pola
yang membedakan ada pada kunjungan dan belanja per kunjungan.
T2 Ada dua cara menjadi pelanggan bernilai tinggi.
Langganan Harian (10 orang) datang hampir
setiap hari (rata-rata 21.4 kali)
dengan belanja kecil (Rp 21.300/kunjungan);
Pembeli Stok (8 orang) jarang datang
(rata-rata 4.5 kali) tetapi belanja
besar tiap kunjungan (Rp 115.000).
T3 Pengunjung Sesekali adalah segmen terbesar.
12 orang (40.0%)
dengan kunjungan menengah 8-14 kali; menu favorit mereka
paling beragam (matcha latte dan teh tawar dominan).
T4 Menu favorit tidak memisahkan segmen.
Kopi susu dan americano muncul di semua segmen; angka
belanja per kunjungan yang membedakan perilaku pelanggan.
5. REKOMENDASI PROGRAM
--------------------------------------------------------------------
R1 Kartu prabayar untuk Langganan Harian.
Deposit (misal Rp 450.000 untuk 20 kali kunjungan) cocok
dengan rutinitas mereka dan mengunci kunjungan harian.
R2 Paket biji kopi untuk Pembeli Stok.
Belanja per kunjungan mereka jauh di atas segmen lain;
tawarkan paket 2x200 gram atau langganan biji bulanan.
R3 Penawaran akhir pekan untuk Pengunjung Sesekali.
Promo bundling Sabtu-Minggu dapat menaikkan frekuensi
kunjungan segmen terbesar ini; efektivitasnya perlu diuji
karena data kita belum mencatat hari kunjungan.
6. KETERBATASAN
--------------------------------------------------------------------
- Jendela data hanya 30 hari; pola musiman belum terlihat.
- Satu baris (Rani Oktaviani) dibuang karena jumlah kunjungan
tidak tercatat; segmen dapat bergeser bila datanya lengkap.
- 30 pelanggan adalah ukuran kecil; segmen beranggota 8 orang
perlu dibaca hati-hati sebelum digeneralisasi.
- menu_favorit hanya menu minuman; pembelian biji kopi pada
segmen Pembeli Stok adalah dugaan dari besaran belanja,
bukan dari catatan produk yang benar-benar dibeli.
Diproses oleh : src/segmentasi.py (pipeline deterministik)
Data : data/pelanggan.csvSembilan Bagian Pengerjaan #
Proyek dikerjakan berurutan; setiap bagian membangun di atas hasil bagian sebelumnya:
- Bagian 1: Memahami Masalah — tiga ide program Bu Rina, dan mengapa “pelanggan rata-rata” menyesatkan analisis.
- Bagian 2: Menyiapkan Data — asal-usul ekspor kasir program member dan struktur berkas CSV.
- Bagian 3: Membersihkan Data
— memperbaiki duplikat, sel kunjungan
"-", dan penulisan kota yang tak konsisten hingga data bersih 30 baris. - Bagian 4: Eksplorasi Data — statistik deskriptif, sebaran kunjungan dan belanja, serta dugaan struktur lewat scatter plot.
- Bagian 5: Membuat Fitur
— menurunkan
belanja_per_kunjungandan menormalisasi fitur denganStandardScaler. - Bagian 6: Menerapkan Clustering
— K-Means
k=3pada fitur ternormalisasi dan penempelan label cluster. - Bagian 7: Mengevaluasi Clustering
— silhouette dan elbow untuk
k=2..6, lalu membela pilihank=3dengan angka. - Bagian 8: Menginterpretasikan Segmen — profil per cluster, penamaan segmen, validasi pengetahuan domain, dan kecocokan program.
- Bagian 9: Menyusun Laporan — membaca laporan akhir kritis, memeriksa ulang angkanya, dan merefleksikan proyek.
Mulailah dari Bagian 1 dan kerjakan sesuai urutan. Seluruh berkas proyek juga dapat diunduh dari bagian Berkas Sumber di bawah halaman ini, serta dari halaman setiap bagian.
Bedah tiga ide program penjualan Bu Rina dan buktikan lewat angka bahwa pelanggan rata-rata hampir tidak mewakili siapa pun.
Buka → # Bagian 2: Menyiapkan DataTelusuri asal-usul ekspor kasir program member lalu baca struktur berkas CSV dan tipe data kelima kolomnya dengan pandas.
Buka → # Bagian 3: Membersihkan DataDiagnosis tiga masalah data mentah — duplikat persis, sel kunjungan "-", dan penulisan kota — lalu susun resep pembersihan yang menghasilkan 30 baris bersih.
Buka → # Bagian 4: Eksplorasi DataBaca statistik deskriptif dan sebaran kunjungan-belanja pelanggan, lalu intip dugaan struktur tiga kelompok lewat scatter plot.
Buka → # Bagian 5: Membuat FiturTurunkan fitur belanja_per_kunjungan, pahami mengapa penyekalaan StandardScaler wajib bagi K-Means, dan kenali risiko kebocoran data.
Buka → # Bagian 6: Menerapkan ClusteringLatih K-Means tiga cluster pada fitur ternormalisasi, tempelkan label ke tiap pelanggan, dan baca pusat cluster dalam satuan asli.
Buka → # Bagian 7: Mengevaluasi ClusteringBandingkan k=2 sampai k=6 dengan silhouette dan elbow inertia, lalu bela pilihan k=3 dengan angka dan pertimbangan bisnis.
Buka → # Bagian 8: Menginterpretasikan SegmenSusun profil anggota tiap cluster, beri nama segmen dengan aturan berbasis angka, dan pasangkan segmen dengan ide program yang paling cocok.
Buka → # Bagian 9: Menyusun LaporanBedah struktur laporan akhir enam seksi, uji ulang angkanya terhadap data, dan tutup proyek dengan refleksi.
Buka →