Notebook Bab 1 - Dari Data Mentah ke Representasi Model

Open In Colab

Notebook Bab 1 ini punya dua bagian. Bagian Demo tinggal Anda jalankan lalu amati keluarannya; bagian Mini Project berisi soal dan data yang Anda kerjakan sendiri.

Representasi (matriks fitur) menentukan batas atas yang bisa dipelajari model. Di notebook ini kita mengubah log transaksi mentah menjadi satu baris fitur per entitas.

Persiapan

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

RANDOM_STATE = 42
rng = np.random.default_rng(RANDOM_STATE)
print('Setup selesai.')
Setup selesai.

Section 1 - Demo: Dari Log Transaksi ke Matriks Fitur

Log transaksi mentah

Tiap pengguna punya banyak baris transaksi. Sebagian pengguna (tipe 1) lebih banyak berbelanja hiburan dengan nominal lebih besar.

n_users = 300
tipe_user = {u: int(rng.integers(0, 2)) for u in range(1, n_users + 1)}

rows = []
for u in range(1, n_users + 1):
    for _ in range(int(rng.integers(5, 25))):
        hari = int(rng.integers(0, 90))
        if tipe_user[u] == 1:
            kat = rng.choice(['makanan', 'transport', 'hiburan', 'tagihan'], p=[0.2, 0.15, 0.5, 0.15])
            nominal = rng.gamma(3.0, 60000)
        else:
            kat = rng.choice(['makanan', 'transport', 'hiburan', 'tagihan'], p=[0.4, 0.3, 0.05, 0.25])
            nominal = rng.gamma(2.0, 40000)
        rows.append((u, hari, round(float(nominal)), kat))

log = pd.DataFrame(rows, columns=['id_user', 'hari', 'nominal', 'kategori'])
log = log.sort_values(['id_user', 'hari']).reset_index(drop=True)
print('Log transaksi mentah:', log.shape)
log.head()
Log transaksi mentah: (4160, 4)
id_user hari nominal kategori
0 1 0 193425 makanan
1 1 9 131381 tagihan
2 1 18 58901 makanan
3 1 20 120017 tagihan
4 1 26 75982 tagihan

Agregasi ke matriks fitur per pengguna

Riwayat banyak baris diringkas menjadi satu baris fitur per id_user.

agg = log.groupby('id_user').agg(
    jml_transaksi=('nominal', 'size'),
    total_nominal=('nominal', 'sum'),
    rata_nominal=('nominal', 'mean'),
    std_nominal=('nominal', 'std'),
    hari_terakhir=('hari', 'max'),
    ragam_kategori=('kategori', 'nunique'),
).fillna(0)
agg['recency'] = 89 - agg['hari_terakhir']
agg['porsi_hiburan'] = (log.assign(hib=(log['kategori'] == 'hiburan'))
                        .groupby('id_user')['hib'].mean())
print('Matriks fitur per pengguna:', agg.shape)
agg.head()
Matriks fitur per pengguna: (300, 8)
jml_transaksi total_nominal rata_nominal std_nominal hari_terakhir ragam_kategori recency porsi_hiburan
id_user
1 12 1120609 93384.083333 52200.122987 87 3 2 0.000000
2 23 4283042 186219.217391 143313.170462 87 4 2 0.391304
3 16 2909412 181838.250000 162838.980598 88 3 1 0.625000
4 15 1041200 69413.333333 47633.586582 77 3 12 0.000000
5 5 563334 112666.800000 61963.126541 79 3 10 0.000000

Representasi menentukan batas atas

Model dan data mentah sama; yang berbeda hanya representasinya. Kita bandingkan matriks fitur agregat melawan satu transaksi mentah per pengguna.

y = agg.index.map(tipe_user).to_numpy()

# (A) dari matriks fitur agregat
Xtr, Xte, ytr, yte = train_test_split(agg, y, test_size=0.3, random_state=RANDOM_STATE, stratify=y)
model = Pipeline([('sc', StandardScaler()), ('lr', LogisticRegression(max_iter=1000))]).fit(Xtr, ytr)
acc_matriks = accuracy_score(yte, model.predict(Xte))

# (B) dari satu transaksi mentah per pengguna (nominal saja)
satu = log.groupby('id_user').first()[['nominal']]
Xtr2, Xte2, ytr2, yte2 = train_test_split(satu, y, test_size=0.3, random_state=RANDOM_STATE, stratify=y)
acc_mentah = accuracy_score(yte2, LogisticRegression(max_iter=1000).fit(Xtr2, ytr2).predict(Xte2))

print(f'Akurasi dari matriks fitur agregat = {acc_matriks:.3f}')
print(f'Akurasi dari satu transaksi mentah  = {acc_mentah:.3f}')
Akurasi dari matriks fitur agregat = 0.989
Akurasi dari satu transaksi mentah  = 0.656

🔎 Amati. Matriks fitur agregat memberi akurasi jauh di atas satu transaksi mentah. Model yang sama dan data mentah yang sama menghasilkan performa berbeda karena representasinya berbeda: itulah maksud representasi menentukan batas atas yang bisa dicapai model.

Section 2 - Mini Project

Soal

Anda menerima log klik (clickstream) sebuah situs, satu baris per klik, dengan kolom id_sesi, menit, halaman, dan durasi_detik. Tujuannya memprediksi konversi (1 = sesi berakhir dengan pembelian).

Tugas:

  1. Ubah log peristiwa menjadi matriks fitur per sesi (minimal 5 fitur turunan, misalnya jumlah klik, total durasi, ragam halaman, durasi rata-rata).
  2. Latih satu baseline classifier pada matriks itu.
  3. Bandingkan dengan prediksi dari satu peristiwa mentah saja.

Luaran: kode agregasi, akurasi kedua pendekatan, dan 2-3 kalimat kesimpulan.

Kriteria penilaian: (a) agregasi benar ke tingkat sesi; (b) minimal 5 fitur turunan; (c) evaluasi pada data uji terpisah.

# DATA AWAL (jangan diubah) - clickstream: satu baris per klik.
n_sesi = 400
konversi_sesi = {s: int(rng.random() < 0.35) for s in range(1, n_sesi + 1)}
baris = []
for s in range(1, n_sesi + 1):
    for _ in range(int(rng.integers(1, 20))):
        menit = round(float(rng.exponential(2.0)), 2)
        halaman = rng.choice(['home', 'produk', 'keranjang', 'promo', 'akun'])
        durasi = int(rng.integers(3, 120) + (30 if konversi_sesi[s] else 0))
        baris.append((s, menit, halaman, durasi))
clicks = pd.DataFrame(baris, columns=['id_sesi', 'menit', 'halaman', 'durasi_detik'])
clicks['konversi'] = clicks['id_sesi'].map(konversi_sesi)
print('Clickstream:', clicks.shape, '| sesi:', clicks['id_sesi'].nunique())
clicks.head()
Clickstream: (4095, 5) | sesi: 400
id_sesi menit halaman durasi_detik konversi
0 1 0.04 promo 62 1
1 1 2.42 akun 51 1
2 1 3.86 produk 41 1
3 1 1.28 promo 89 1
4 1 1.09 produk 135 1
# Kerjakan di sini.
# Petunjuk: clicks.groupby('id_sesi').agg(...) untuk membuat matriks fitur per sesi.