Semua ketidakpastian yang kita laporkan pada chapter sebelumnya berlaku untuk dunia yang diam. Kenyataannya dunia terus bergerak: musim berganti, harga beras naik, kompetitor membuka cabang di seberang jalan, dan perilaku pelanggan bergeser pelan-pelan tanpa memberi tahu. Model yang hari ini akurat bisa diam-diam menjadi usang — ia tetap percaya diri, tetapi kepercayaannya tidak lagi berpihak pada kita. Pada chapter ini kita belajar mendeteksi pergeseran itu sebelum merugikan: membedakan data drift dari concept drift, memantau distribusi fitur antar periode dengan groupby, dan merumuskan sinyal kapan model harus dilatih ulang.
Mengapa Model Memburuk Seiring Waktu #
Model machine learning belajar dari potret masa lalu. Saat pelatihan, ia menangkap pola hubungan antara fitur dan target persis seperti yang berlaku pada data latih — dan evaluasi kita pun mengukur kualitas pada potret yang sama. Begitu model dipakai di produksi, ia mulai bertemu masa kini: pelanggan baru, menu baru, kebiasaan belanja yang bergeser. Selama masa kini masih menyerupai masa lalu, prediksi tetap sahih; begitu kemiripannya memudar, kualitas model melorot tanpa model itu sendiri berubah sebaris kode pun.
Ini bukan skenario langka. Survei praktik machine learning berkali-kali menemukan bahwa memantau dan memelihara model setelah rilis justru menyita porsi pekerjaan terbesar dibanding membangunnya. Deteksi dini adalah gembok pagar pertama: lebih murah menemukan drift pada dashboard mingguan daripada menemukannya dari keluhan pelanggan yang tidak kebagian stok.
Data Drift vs Concept Drift #
Pergeseran datang dalam dua rupa yang berbeda sumber dan beda obatnya. Data drift (atau covariate shift) terjadi ketika distribusi fitur berubah — nilai transaksi pelanggan naik, jam kunjungan bergesir ke sore hari — tanpa peduli hubungan fitur-target. Concept drift terjadi ketika hubungan itu sendiri yang berubah: pola yang dulu benar kini keliru.
| Aspek | Data drift | Concept drift |
|---|---|---|
| Yang bergeser | distribusi fitur input | hubungan fitur terhadap target |
| Contoh di kedai | nilai transaksi naik karena harga menu naik | pelanggan besar tidak lagi identik dengan member setelah syarat member diubah |
| Terlihat dari | statistik fitur antar periode (mean, std) | akurasi model yang melorot meski fitur terlihat normal |
| Bahaya bila diabaikan | prediksi di luar wilayah yang dikenal model | model salah arah secara sistematis |
| Obatnya | perluas data latih, pantau terus | latih ulang dengan data terbaru, tinjau fitur |
Pembedaan ini penting karena keduanya bisa muncul sendiri-sendiri atau bersamaan. Data drift tanpa concept drift masih bisa ditoleransi untuk beberapa waktu — model tetap valid di wilayah yang dikenalnya. Concept drift hampir selalu menuntut tindakan, karena keputusan yang diambil model mulai salah dasarnya.
Memantau Distribusi Fitur Antar Periode #
Langkah paling murah dan paling dianjurkan adalah mencetak statistik ringkas fitur per periode — pekerjaan groupby yang sudah akrab bagi kita. Toko Kopi Kita mencatat tiga puluh transaksi sampel selama tiga bulan terakhir di transaksi_bulanan.csv:
bulan,nilai_transaksi,jumlah_item,member
2025-10,24.5,2,0
2025-10,31.0,1,0
2025-10,38.2,3,0
2025-10,42.7,2,0
2025-10,49.9,4,0
2025-10,56.1,1,1
2025-10,61.4,3,1
2025-10,68.0,2,1
2025-10,74.8,4,1
2025-10,79.3,3,1
2025-11,25.8,3,0
2025-11,33.1,1,0
2025-11,40.5,2,0
2025-11,47.2,3,0
2025-11,52.6,2,0
2025-11,58.3,4,1
2025-11,63.9,1,1
2025-11,70.1,3,1
2025-11,76.4,2,1
2025-11,81.7,4,1
2025-12,48.2,4,0
2025-12,53.7,3,0
2025-12,59.1,5,0
2025-12,64.8,3,0
2025-12,69.3,4,0
2025-12,72.6,6,0
2025-12,78.4,4,1
2025-12,83.0,5,1
2025-12,87.5,6,1
2025-12,92.1,4,1Kita hitung mean dan simpangan baku tiap fitur per bulan:
import pandas as pd
df = pd.read_csv("transaksi_bulanan.csv")
print(f"Total {len(df)} transaksi dari {df['bulan'].nunique()} bulan")
print(df.groupby("bulan").agg(
rata_nilai=("nilai_transaksi", "mean"),
simpangan_nilai=("nilai_transaksi", "std"),
rata_item=("jumlah_item", "mean"),
proporsi_member=("member", "mean"),
).round(1))Output yang Dihasilkan:
Total 30 transaksi dari 3 bulan
rata_nilai simpangan_nilai rata_item proporsi_member
bulan
2025-10 52.6 18.6 2.5 0.5
2025-11 55.0 18.6 2.5 0.5
2025-12 70.9 14.6 4.4 0.4Mari kita baca tabelnya seperti seorang petugas pemantau:
- Oktober ke November nyaris diam: rata-rata nilai transaksi 52,6 → 55,0 (+4%), jumlah item tetap 2,5, proporsi member tetap 0,5 — variasi bulan-ke-bulan yang wajar.
- Desember melompat: rata-rata nilai transaksi naik ke 70,9 (+29% dari November) dan rata-rata item per keranjang dari 2,5 ke 4,4. Toko baru saja merilis paket bundling akhir tahun, jadi lompatan ini masuk akal — tetapi model prediksi member kita tidak pernah melihat pola seperti ini saat berlatih.
- Proporsi member turun tipis (0,5 → 0,4) padahal belanja justru naik — petunjuk pertama bahwa hubungan lama “belanja besar = member” mulai tidak berlaku.
Membandingkan Dua Periode dengan Aturan Sederhana #
Mata telanjang cukup untuk tiga puluh baris, tetapi pemantauan sungguhan melibatkan puluhan fitur dan berbulan-bulan data. Kita padatkan menjadi satu fungsi pembanding dua periode dengan ambang sederhana: bila rata-rata fitur bergeser lebih dari 10% dari periode acuan, tandai sebagai DRIFT.
import pandas as pd
df = pd.read_csv("transaksi_bulanan.csv")
def bandingkan_periode(df, awal, akhir, ambang=10.0):
"""Bandingkan distribusi tiap fitur antara dua periode (dalam persen)."""
baris = []
for fitur in ["nilai_transaksi", "jumlah_item"]:
a = df[df["bulan"] == awal][fitur]
b = df[df["bulan"] == akhir][fitur]
perubahan = (b.mean() - a.mean()) / a.mean() * 100
baris.append({
"fitur": fitur,
"mean_awal": round(a.mean(), 1),
"mean_akhir": round(b.mean(), 1),
"perubahan_persen": round(perubahan, 1),
"std_awal": round(a.std(), 1),
"std_akhir": round(b.std(), 1),
"status": "DRIFT" if abs(perubahan) > ambang else "stabil",
})
return pd.DataFrame(baris)
print(bandingkan_periode(df, "2025-10", "2025-12").to_string(index=False))
print()
print(bandingkan_periode(df, "2025-10", "2025-11").to_string(index=False))Output yang Dihasilkan:
fitur mean_awal mean_akhir perubahan_persen std_awal std_akhir status
nilai_transaksi 52.6 70.9 34.8 18.6 14.6 DRIFT
jumlah_item 2.5 4.4 76.0 1.1 1.1 DRIFT
fitur mean_awal mean_akhir perubahan_persen std_awal std_akhir status
nilai_transaksi 52.6 55.0 4.5 18.6 18.6 stabil
jumlah_item 2.5 2.5 0.0 1.1 1.1 stabilBedah fungsinya:
- Baris 9-11 (saring dua periode):
df[df["bulan"] == awal]mengambil potongan data bulan acuan dan bulan terbaru; dua Series per fitur inilah yang dibandingkan. - Baris 12 (perubahan relatif): selisih rata-rata dibagi rata-rata acuan — 34,8% untuk nilai transaksi dan 76,0% untuk jumlah item Oktober-Desember, keduanya jauh melampaui ambang 10%.
- Pembanding kedua (Okt-Nov): sengaja dicetak sebagai kontrol — pergeseran 4,5% dan 0% berstatus stabil, jadi aturan ini tidak berteriak pada variasi bulanan yang normal. Aturan sederhana yang tidak bisa membedakan gempa dari getaran tak akan lama dipercaya petugas pemantau.
Perlu jujur soal batas aturan ini: ambang 10% dipilih berdasarkan kesepakatan tim, bukan hukum alam. Untuk fitur yang sensitif (misalnya persentasi pesanan dibatalkan), pergeseran 5% pun bisa serius; untuk fitur yang riuh, 20% masih wajar. Pemantauan yang lebih kuat memakai uji statistik perubahan distribusi, tetapi aturan ambang seperti ini tetap menjadi lapisan pertama yang mudah dibaca semua orang di tim.
Concept Drift Terlihat dari Akurasi yang Melorot #
Data drift bicara tentang fitur; concept drift hanya bisa dibuktikan lewat kinerja model terhadap label terbaru. Kita latih model prediksi member pada data Oktober-November, lalu ukur akurasinya di tiap bulan:
import pandas as pd
from sklearn.linear_model import LogisticRegression
df = pd.read_csv("transaksi_bulanan.csv")
fitur = ["nilai_transaksi", "jumlah_item"]
latih = df[df["bulan"].isin(["2025-10", "2025-11"])]
model = LogisticRegression(max_iter=1000).fit(latih[fitur], latih["member"])
print(f"Model dilatih pada {len(latih)} transaksi Oktober-November")
for bulan in ["2025-10", "2025-11", "2025-12"]:
sub = df[df["bulan"] == bulan]
print(f"akurasi {bulan}: {model.score(sub[fitur], sub['member']):.2f} ({len(sub)} transaksi)")Output yang Dihasilkan:
Model dilatih pada 20 transaksi Oktober-November
akurasi 2025-10: 1.00 (10 transaksi)
akurasi 2025-11: 1.00 (10 transaksi)
akurasi 2025-12: 0.60 (10 transaksi)Mari kita telaahnya:
- Baris 7-8 (melatih potret lama): hubungan di Oktober-November sangat bersih — pelanggan bersumbangan belanja besar memang member — sehingga model menemukan polanya sempurna (akurasi latih 1,00; pada data nyata, angka sebersih ini patut dicurigai).
- Baris 11-13 (uji per bulan): model tetap sempurna di kedua bulan latih, lalu jatuh ke 0,60 pada Desember. Empat dari sepuluh transaksi Desember salah diklasifikasi.
- Akar masalahnya bukan fitur, melainkan makna: sejak Desember tim loyalitas menaikkan syarat member — hanya belanja di atas 78 ribu yang tetap member. Model lama masih menganggap semua belanja di atas 55 ribu sebagai member, sehingga transaksi 59-72 ribu diprediksi member padahal bukan. Hubungan fitur-target yang dipelajarinya sudah tidak berlaku: itulah concept drift.
Sinyal Retraining: Kapan Model Dilatih Ulang #
Dari pengalaman kedai ini, kita bisa menyusun urutan tindakan yang berlapis. Pemantauan mingguan mencetak statistik fitur (blok pertama) dan membandingkannya dengan periode acuan (blok kedua); begitu status DRIFT muncul, langkah berikutnya bukan langsung melatih ulang, melainkan mengumpulkan label terbaru dan mengukur akurasi model (blok ketiga). Retraining dijadwalkan bila akurasi turun di bawah ambang layanan — misalnya 0,75 untuk kasus kita — atau bila pola kesalahan mulai terkonsentrasi pada satu segmen pelanggan.
Dua kebiasaan kecil menjaga siklus ini tetap sehat. Pertama, simpan cuplikan data berlabel setiap periode sebagai golden set — tanpa bahan itu, akurasi bulan berjalan tidak bisa dihitung dan concept drift tidak akan pernah terlihat sampai menjadi keluhan. Kedua, catat versi data dan versi model setiap pelatihan ulang, melanjutkan kebiasaan pencatatan asal data dari chapter Pencatatan Asal Data — ketika model baru ternyata lebih buruk, kita perlu tahu persis ia belajar dari apa.
Rangkuman #
- Model adalah potret masa lalu; drift adalah ukuran seberapa jauh masa kini sudah meninggalkannya — dan ia tidak memperbaiki diri sendiri.
- Data drift menggeser distribusi fitur (terlihat dari mean/std antar periode), sedangkan concept drift mengubah hubungan fitur-target (terlihat dari akurasi yang melorot meski fitur tampak wajar).
- Pemantauan termurah:
groupbyperiode plus statistik ringkas, lalu fungsi pembanding dua periode dengan ambang perubahan relatif — dengan kontrol periode normal agar tidak salah alarm. - Kasus kedai kita: nilai transaksi +34,8% dan item +76,0% (data drift) berjalan seiring akurasi 1,00 → 0,60 (concept drift) setelah syarat member berubah.
- Keputusan retraining berbasis bukti kinerja pada label terbaru, bukan refleks terhadap alarm fitur; golden set dan pencatatan versi adalah bahan bakunya.
Waktu adalah satu dimensi pergeseran; ruang adalah dimeninya yang lain. Kedai yang ramai di satu kecamatan bisa sepi di kecamatan sebelah, dan keputusan ekspansi cabang bertumpu pada pertanyaan “di mana” — bukan hanya “berapa”. Setelah memantau pergeseran antar waktu, kita berpindah ke analisis antar wilayah pada chapter berikutnya: Analisis Geospasial .
Referensi: