Promosi yang sama untuk semua pelanggan adalah pemborosan: pelanggan yang tiap pekan datang butuh perlakuan yang berbeda dari pelanggan yang tiga bulan tak muncul. Segmentasi memecah masalah itu dengan membagi pelanggan ke dalam kelompok yang polanya mirip, sehingga tiap kelompok bisa disasarkan dengan tindakan yang sesuai. Pada chapter ini kita membangun segmentasi pelanggan Toko Kopi Kita dari jejak transaksinya dengan kerangka RFM — recency, frequency, monetary — lalu memberi skor lewat pd.cut dan pd.qcut, menyusun segmen dengan aturan yang bisa kita jelaskan, dan memprofil tiap segmen sebelum akhirnya membandingkan cara ini dengan clustering.
Konsep RFM: Tiga Pertanyaan tentang Setiap Pelanggan #
RFM meringkas perilaku belanja setiap pelanggan menjadi tiga angka yang menjawab tiga pertanyaan:
| Dimensi | Pertanyaan | Cara hitung dari data transaksi |
|---|---|---|
| Recency | Sudah berapa hari sejak transaksi terakhirnya? | batas analisis dikurangi tanggal transaksi terakhir |
| Frequency | Sudah berapa kali pelanggan bertransaksi? | banyaknya baris transaksi atas namanya |
| Monetary | Sudah berapa besar total belanjanya? | penjumlahan nilai transaksinya |
Ketiganya dipilih karena saling melengkapi. Recency menangkap kehangatan hubungan — pelanggan yang baru datang kemarin masih dekat di ingatan. Frequency menangkap kebiasaan — pelanggan yang sudah dua puluh kali datang punya pola yang berbeda dari yang baru sekali. Monetary menangkap nilai — pelanggan dengan total belanja besar layak dilayani lebih. Pelanggan yang bagus di ketiga dimensi sekaligus adalah aset; yang buruk di ketiganya perlu perhatian berbeda; dan yang mencampur — misalnya dulu besar tapi lama tak kembali — justru sering paling menarik untuk diselidiki.
Dari Transaksi Menjadi Tabel RFM #
Bahan kita adalah catatan 40 transaksi dari 10 pelanggan selama hampir tiga bulan, tersimpan sebagai berkas CSV pada bundle chapter ini:
tanggal,pelanggan,total
2025-10-19,tono,44
2025-10-20,tono,46
2025-10-21,tono,43
2025-10-22,tono,47
2025-10-24,lina,26
2025-10-26,agus,40
2025-10-30,joko,28
2025-11-03,budi,52
2025-11-05,lina,24
2025-11-06,agus,44
2025-11-07,dewi,38
2025-11-10,siti,45
2025-11-11,joko,32
2025-11-12,rani,35
2025-11-17,agus,39
2025-11-18,budi,55
2025-11-20,joko,30
2025-11-21,dewi,42
2025-11-24,siti,50
2025-11-26,rani,33
2025-11-28,agus,45
2025-11-29,mira,36
2025-12-01,budi,48
2025-12-05,dewi,40
2025-12-08,siti,47
2025-12-10,rani,38
2025-12-11,mira,40
2025-12-14,budi,60
2025-12-16,dewi,39
2025-12-18,mira,38
2025-12-19,siti,52
2025-12-22,rani,34
2025-12-27,budi,51
2025-12-29,farhan,50
2025-12-30,dewi,41
2026-01-02,siti,48
2026-01-05,farhan,54
2026-01-08,budi,58
2026-01-10,siti,48
2026-01-12,budi,61Satu baris transaksi belum bercerita apa-apa tentang pelanggan; kita perlu meringkasnya per pelanggan. groupby dengan agregasi bernama melakukan semuanya sekaligus:
import pandas as pd
transaksi = pd.read_csv("transaksi_rfm.csv", parse_dates=["tanggal"])
batas_data = pd.Timestamp("2026-01-15")
rfm = transaksi.groupby("pelanggan").agg(
transaksi_terakhir=("tanggal", "max"),
frekuensi=("tanggal", "count"),
monetary=("total", "sum"),
)
rfm["recency"] = (batas_data - rfm["transaksi_terakhir"]).dt.days
print("Jumlah transaksi:", len(transaksi), "| jumlah pelanggan:", len(rfm))
print(rfm[["recency", "frekuensi", "monetary"]].sort_values("recency"))Output yang Dihasilkan:
Jumlah transaksi: 40 | jumlah pelanggan: 10
recency frekuensi monetary
pelanggan
budi 3 7 385
siti 5 6 290
farhan 10 2 104
dewi 16 5 200
rani 24 4 140
mira 28 3 114
agus 48 4 168
joko 56 3 90
lina 71 2 50
tono 85 4 180Bedah kode di atas:
pd.read_csv(..., parse_dates=["tanggal"])memuat berkas sekaligus mengubah kolom tanggal menjadi tipe datetime — tanpa itu, pengurangan tanggal hanya mengurangi teks.- Agregasi bernama pada
agg()memasangkan nama kolom hasil dengan pasangan("kolom sumber", "fungsi"):transaksi_terakhirmengambil tanggal maksimum,frekuensimenghitung baris,monetarymenjumlahkan nilai. Satugroupbymenghasilkan ketiga kolom sekaligus. rfm["recency"]menghitung selisih hari antara batas analisis dan transaksi terakhir lewat.dt.days. Angka 85 milik tono berarti transaksi terakhirnya 85 hari sebelum batas — pelanggan yang paling lama absen.sort_values("recency")menyusun tabel dari pelanggan paling hangat ke paling dingin, sehingga gradasi hubungan langsung terlihat.
Memberi Skor dengan cut dan qcut #
Tabel RFM berisi angka mentah dengan satuan berbeda-beda — hari, kali, ribu rupiah — sehingga belum bisa dibandingkan langsung. Kita terjemahkan tiap dimensi menjadi skor 1 sampai 3. Untuk itu pandas menyediakan dua pisau yang berbeda:
| Alat | Dasar pembagian | Kapan cocok |
|---|---|---|
pd.cut | batas rentang yang kita tetapkan | ambang sudah punya makna bisnis (mis. “lebih dari 30 hari = dingin”) |
pd.qcut | kuantil data itu sendiri | ingin kelompok yang berukuran sebanding (mis. sepertiga teratas) |
Untuk recency kita pakai pd.cut dengan ambang bisnis — 14 hari dan 30 hari; untuk frequency dan monetary kita pakai pd.qcut agar tiap tingkat skor terisi kurang lebih sama banyak:
import pandas as pd
transaksi = pd.read_csv("transaksi_rfm.csv", parse_dates=["tanggal"])
batas_data = pd.Timestamp("2026-01-15")
rfm = transaksi.groupby("pelanggan").agg(
transaksi_terakhir=("tanggal", "max"),
frekuensi=("tanggal", "count"),
monetary=("total", "sum"),
)
rfm["recency"] = (batas_data - rfm["transaksi_terakhir"]).dt.days
rfm["skor_R"] = pd.cut(rfm["recency"], bins=[-1, 14, 30, 1000], labels=[3, 2, 1]).astype(int)
rfm["skor_F"] = pd.qcut(rfm["frekuensi"].rank(method="first"), q=3, labels=[1, 2, 3]).astype(int)
rfm["skor_M"] = pd.qcut(rfm["monetary"].rank(method="first"), q=3, labels=[1, 2, 3]).astype(int)
print(rfm[["recency", "frekuensi", "monetary", "skor_R", "skor_F", "skor_M"]].sort_values("recency"))Output yang Dihasilkan:
recency frekuensi monetary skor_R skor_F skor_M
pelanggan
budi 3 7 385 3 3 3
siti 5 6 290 3 3 3
farhan 10 2 104 3 1 1
dewi 16 5 200 2 3 3
rani 24 4 140 2 2 2
mira 28 3 114 2 2 2
agus 48 4 168 1 2 2
joko 56 3 90 1 1 1
lina 71 2 50 1 1 1
tono 85 4 180 1 3 3Bedah kode dan hasilnya:
pd.cut(rfm["recency"], bins=[-1, 14, 30, 1000], labels=[3, 2, 1])memetakan recency ke skor: 0-14 hari → 3, 15-30 hari → 2, lebih dari 30 hari → 1. Batas bawah-1memastikan nilai 0 ikut ke rentang pertama; batas atas1000cukup lebar untuk data kita. Perhatikan arah labelnya dibalik — makin segar makin tinggi skornya..rank(method="first")pada kolom frekuensi menangani nilai seri: dua pelanggan dengan 4 transaksi mendapat nomor urut berbeda sesuai urutan alfabet, sehinggaqcuttidak tersandung batas kuantil yang tumpang tindih.pd.qcut(..., q=3)membagi pelanggan menjadi tiga kelompok berukuran sama berdasarkan peringkatnya. Hasilnya pada kolomskor_F: farhan dan lina (2 transaksi) berada di tingkat 1; agus, rani, dan tono di tingkat 2; dewi, siti, dan budi di tingkat 3.- Tabel skor memunculkan karakter yang mulai berlapis: budi dan siti bersih di ketiga dimensi; farhan segar tetapi jarang; tono — recency terburuk (85 hari) namun monetary terbaik ketiga (skor 3). Satu dimensi saja tidak akan menangkap tono; gabungan ketiganya menangkapnya dengan jelas.
Menyusun Segmen dan Memprofilnya #
Skor akhirnya perlu menjadi segmen bernama yang bisa dipahami tim pemasaran. Kita terjemahkan skor menjadi segmen lewat aturan — memakai numpy.select yang membaca daftar kondisi berurutan:
import numpy as np
import pandas as pd
transaksi = pd.read_csv("transaksi_rfm.csv", parse_dates=["tanggal"])
batas_data = pd.Timestamp("2026-01-15")
rfm = transaksi.groupby("pelanggan").agg(
transaksi_terakhir=("tanggal", "max"),
frekuensi=("tanggal", "count"),
monetary=("total", "sum"),
)
rfm["recency"] = (batas_data - rfm["transaksi_terakhir"]).dt.days
rfm["skor_R"] = pd.cut(rfm["recency"], bins=[-1, 14, 30, 1000], labels=[3, 2, 1]).astype(int)
rfm["skor_F"] = pd.qcut(rfm["frekuensi"].rank(method="first"), q=3, labels=[1, 2, 3]).astype(int)
rfm["skor_M"] = pd.qcut(rfm["monetary"].rank(method="first"), q=3, labels=[1, 2, 3]).astype(int)
rfm["segmen"] = np.select(
[
(rfm["skor_R"] == 3) & (rfm["skor_F"] >= 2),
(rfm["skor_R"] == 3) & (rfm["skor_F"] == 1),
rfm["skor_R"] == 1,
],
["pelanggan utama", "pelanggan baru", "berisiko hilang"],
default="perlu dijaga",
)
profil = rfm.groupby("segmen").agg(
jumlah=("recency", "size"),
recency=("recency", "mean"),
frekuensi=("frekuensi", "mean"),
monetary=("monetary", "mean"),
).round(1)
print(profil)
print()
for segmen in sorted(rfm["segmen"].unique()):
anggota = sorted(rfm.index[rfm["segmen"] == segmen].tolist())
print("%-16s: %s" % (segmen, ", ".join(anggota)))Output yang Dihasilkan:
jumlah recency frekuensi monetary
segmen
berisiko hilang 4 65.0 3.2 122.0
pelanggan baru 1 10.0 2.0 104.0
pelanggan utama 2 4.0 6.5 337.5
perlu dijaga 3 22.7 4.0 151.3
berisiko hilang : agus, joko, lina, tono
pelanggan baru : farhan
pelanggan utama : budi, siti
perlu dijaga : dewi, mira, raniBedah kode dan pembacaan hasilnya:
np.select(kondisi, pilihan, default=...)mengevaluasi daftar kondisi dari atas: pelanggan dengan recency segar dan frekuensi minimal 2 menjadipelanggan utama; yang segar tetapi baru sekali-kali datang menjadipelanggan baru; yang recency-nya buruk menjadiberisiko hilang; sisanyaperlu dijaga. Aturan seperti ini transparan — siapa pun bisa menjelaskan mengapa seorang pelanggan masuk segmen tertentu.- Profil per segmen merangkum karakter rata-ratanya: pelanggan utama rata-rata bertransaksi 6,5 kali dengan monetary 337,5 ribu; berisiko hilang sudah rata-rata 65 hari tidak muncul.
- Daftar anggota memperlihatkan siapa saja di tiap segmen — dan di sinilah tono muncul sebagai temuan paling berharga: pelanggan dengan skor monetary 3 yang sudah 85 hari tidak kembali. Rata-rata monetary segmen berisiko hilang (122) menyembunyikan tono; daftar anggota membuka tabirnya.
Segmen baru berguna bila tiap segmen punya tindakan yang jelas:
| Segmen | Ciri | Tindakan yang masuk akal |
|---|---|---|
| pelanggan utama | segar, sering, besar | jaga pengalaman; akses prioritas menu baru |
| pelanggan baru | segar tetapi jarang | tawaran kunjungan kedua; kenalkan program loyalitas |
| perlu dijaga | mulai menjauh | pengingat personal; promo terbatas waktu |
| berisiko hilang | lama tidak kembali | kampanye menarik kembali; telusuri keluhan lama |
Dari Aturan ke Clustering #
Segmentasi di atas berdiri di atas dua pilihan yang kita buat: ambang recency (14 dan 30 hari) dan aturan penamaan segmen. Pendekatan berbasis aturan seperti ini mudah dijelaskan dan cepat dibangun, tetapi ambangnya tetap tebakan terdidik. Clustering — yang akan kita pelajari pada Section Unsupervised Learning — mengambil jalan berbeda: algoritma seperti K-Means membiarkan pola di data yang menentukan kelompoknya, tanpa ambang dari kita. Imbalannya adalah interpretasi: kelompok hasil clustering harus kita maknai setelahnya, sedangkan segmen RFM bermakna sejak awal. Keduanya bukan saingan — praktikum yang baik sering memulai dari aturan RFM, lalu menguji apakah clustering menemukan potongan pasar yang terlewat.
Rangkuman #
- RFM meringkas pelanggan menjadi tiga angka: recency (segar tidaknya), frequency (sering tidaknya), monetary (besar tidaknya) — dihitung dari data transaksi lewat
groupbydengan agregasi bernama. pd.cutmembagi menurut ambang yang kita tetapkan;pd.qcutmembagi menurut kuantil data. Nilai seri padaqcutditangani denganrank(method="first").- Segmen disusun dari skor dengan aturan yang transparan —
np.selectmembaca daftar kondisi berurutan — lalu diprofil per segmen untuk memastikan tiap segmen bermakna dan bisa ditindaklanjuti. - Profil rata-rata bisa menyembunyikan kasus menarik seperti tono; selalu periksa daftar anggota segmen, bukan hanya ringkasannya.
- Pendekatan berbasis aturan melengkapinya clustering nanti: aturan transparan sejak awal, clustering menemukan pola yang tidak kita duga.
Kita kini punya cara membagi dunia menjadi kelompok yang bermakna — dan sepanjang Section ini kita telah menghasilkan banyak temuan: hubungan kategorikal, perbedaan antar kelompok, segmen pelanggan. Pertanyaan yang tersisa sebelum melaporkan: temuan mana yang benar-benar tahan uji, dan bagaimana kita mengujinya tanpa terjebak mengarang cerita setelah melihat hasil? Jawabannya adalah disiplin analisis konfirmasi pada chapter berikutnya. Lanjut ke chapter Analisis Konfirmasi .