Satu pelanggan bisa memiliki tiga puluh baris transaksi; satu toko bisa memiliki ribuan. Model yang memprediksi “apakah pelanggan ini akan berhenti berlangganan?” tidak menerima tiga puluh baris — ia menerima satu baris per pelanggan, dan baris itu harus meringkas seluruh riwayatnya. Agregasi fitur adalah tekniknya: merangkum banyak baris menjadi beberapa statistik per entitas dengan groupby, lalu menempelkan hasilnya kembali ke tabel utama dengan merge. Pada chapter ini kita bangun fitur pelanggan dari data transaksi Toko Kopi Kita: jumlah transaksi, rata-rata belanja, total belanja, dan recency — berapa hari sejak terakhir belanja.

Dari Banyak Baris ke Satu Baris per Pelanggan #

Data mentah kita mencatat empat belas transaksi dari lima pelanggan, lengkap dengan tanggal dan nilai belanja. Tabel semacam ini disebut data level transaksi; model pelanggan membutuhkan data level entitas. Jembatannya adalah groupby + agg:

python
import pandas as pd

transaksi = pd.DataFrame({
    "pelanggan": ["Budi", "Budi", "Budi", "Siti", "Siti",
                  "Dewi", "Dewi", "Rani", "Rani", "Rani",
                  "Agus", "Agus", "Siti", "Budi"],
    "tanggal": pd.to_datetime([
        "2026-01-02", "2026-01-08", "2026-01-15", "2026-01-03",
        "2026-01-12", "2026-01-05", "2026-01-18", "2026-01-04",
        "2026-01-09", "2026-01-16", "2026-01-07", "2026-01-14",
        "2026-01-20", "2026-01-21",
    ]),
    "nilai_ribu": [42, 25, 38, 55, 30, 22, 48, 65, 35, 28, 18, 24, 50, 33],
})

fitur_pelanggan = transaksi.groupby("pelanggan").agg(
    jumlah_transaksi=("nilai_ribu", "count"),
    rata_belanja=("nilai_ribu", "mean"),
    total_belanja=("nilai_ribu", "sum"),
).round(1)
print(fitur_pelanggan)

Output yang Dihasilkan:

text
           jumlah_transaksi  rata_belanja  total_belanja
pelanggan                                               
Agus                      2          21.0             42
Budi                      4          34.5            138
Dewi                      2          35.0             70
Rani                      3          42.7            128
Siti                      3          45.0            135

Mari kita bedah baris demi baris:

  1. Baris 16-20 (groupby("pelanggan").agg(...)): groupby mengelompokkan baris menurut pelanggan, lalu agg menghitung beberapa statistik sekaligus — empat belas baris terlipat menjadi lima.
  2. Baris 17 (jumlah_transaksi=("nilai_ribu", "count")): pasangan (kolom, fungsi) memberi tahu agregasi mana dijalankan pada kolom mana: hitung banyaknya baris dari kolom nilai_ribu. Nama di kiri tanda sama dengan sekaligus menjadi nama kolom hasil — fitur bernama rapi tanpa perlu mengubah nama setelahnya.
  3. Baris 18-19 (mean, sum): rata-rata dan total belanja per pelanggan. Perhatikan karakter yang muncul: Siti jarang datang tetapi belanja besar sekali datang (rata-rata 45,0); Agus datang sesering Dewi tetapi belanjanya paling kecil (rata-rata 21,0).
  4. Baris 20 (.round(1)): pembulatan agar tabel terbaca rapi. Baris hasil groupby tersusun menurut indeks — di sini nama pelanggan — secara alfabetis, bukan urutan kemunculan.

Recency dan Penggabungan ke Tabel Utama #

Statistik belanja menangkap “berapa dan berapa sering”, tetapi satu dimensi penting masih hilang: “seberapa baru”. Recency dihitung dari tanggal transaksi terakhir dikurangi tanggal acuan — dan penahapannya mengajarkan pola baru: agregasi kolom tanggal, bukan kolom angka. Setelah lengkap, seluruh fitur digabungkan ke tabel pelanggan induk dengan merge:

python
import pandas as pd

transaksi = pd.DataFrame({
    "pelanggan": ["Budi", "Budi", "Budi", "Siti", "Siti",
                  "Dewi", "Dewi", "Rani", "Rani", "Rani",
                  "Agus", "Agus", "Siti", "Budi"],
    "tanggal": pd.to_datetime([
        "2026-01-02", "2026-01-08", "2026-01-15", "2026-01-03",
        "2026-01-12", "2026-01-05", "2026-01-18", "2026-01-04",
        "2026-01-09", "2026-01-16", "2026-01-07", "2026-01-14",
        "2026-01-20", "2026-01-21",
    ]),
    "nilai_ribu": [42, 25, 38, 55, 30, 22, 48, 65, 35, 28, 18, 24, 50, 33],
})

pelanggan = pd.DataFrame({
    "pelanggan": ["Budi", "Siti", "Dewi", "Rani", "Agus"],
    "kota": ["Bandung", "Bandung", "Surabaya", "Medan", "Bandung"],
})

acuan = pd.Timestamp("2026-01-31")
fitur = transaksi.groupby("pelanggan").agg(
    jumlah_transaksi=("nilai_ribu", "count"),
    rata_belanja=("nilai_ribu", "mean"),
    terakhir_belanja=("tanggal", "max"),
)
fitur["recency_hari"] = (acuan - fitur["terakhir_belanja"]).dt.days
fitur["rata_belanja"] = fitur["rata_belanja"].round(1)

tabel_lengkap = pelanggan.merge(fitur, on="pelanggan", how="left")
print(tabel_lengkap.to_string(index=False))

Output yang Dihasilkan:

text
pelanggan     kota  jumlah_transaksi  rata_belanja terakhir_belanja  recency_hari
     Budi  Bandung                 4          34.5       2026-01-21            10
     Siti  Bandung                 3          45.0       2026-01-20            11
     Dewi Surabaya                 2          35.0       2026-01-18            13
     Rani    Medan                 3          42.7       2026-01-16            15
     Agus  Bandung                 2          21.0       2026-01-14            17

Inilah pekerjaan tiap barisnya:

  1. Baris 25 (terakhir_belanja=("tanggal", "max")): agregasi tidak terbatas pada angka — max pada kolom tanggal mengembalikan transaksi terakhir tiap pelanggan, tetap berupa tipe datetime.
  2. Baris 27 ((acuan - fitur["terakhir_belanja"]).dt.days): recency dihitung terhadap tanggal acuan yang kita tetapkan (akhir Januari 2026). Pengurangan datetime menghasilkan durasi, dan .dt.days mengambil jumlah harinya. Angka kecil berarti baru saja belanja; angka Agus (17) berarti hampir tiga pekan tanpa kunjungan.
  3. Baris 30 (pelanggan.merge(fitur, on="pelanggan", how="left")): tabel fitur ditempelkan ke tabel pelanggan berdasarkan kolom kunci pelanggan. how="left" menjaga seluruh baris tabel kiri — pelanggan tanpa satu pun transaksi tetap hadir dengan nilai kosong, bukan hilang diam-diam.
  4. Hasil akhir: satu baris per pelanggan yang kini memuat identitas (kota) sekaligus perilaku belanja (frekuensi, nilai, kapan terakhir) — bentuk persis yang diminta model klasifikasi pelanggan.

Rangkuman #

  • groupby(entitas).agg(nama=(kolom, fungsi), ...) melipat data level transaksi menjadi fitur level entitas; pasangan penamaan langsung memberi nama kolom hasil.
  • Statistik dasar per entitas: count untuk frekuensi, mean dan sum untuk nilai; kolom tanggal diagregasi dengan max untuk menangkap kejadian terakhir.
  • Recency = selisih tanggal acuan beku dengan transaksi terakhir, diambil lewat .dt.days; angka ini sering menjadi fitur prediktif terkuat untuk churn.
  • merge(on=kunci, how="left") menempelkan fitur agregat kembali ke tabel utama tanpa kehilangan baris entitas yang tidak punya riwayat.

Fitur pelanggan kita kini lengkap — tetapi jangan buru-buru beranjak: makin banyak fitur, makin besar peluang dua di antaranya mengukur hal yang sama. Kolom kembar semacam itu mengaburkan interpretasi model, dan cara menemukannya adalah pokok bahasan chapter berikutnya.

Referensi #

  1. pandas — DataFrame.groupby
  2. pandas — DataFrame.merge
  3. pandas — User Guide