Nilai pada sebuah deret waktu berasal dari kejadian yang berurutan, sehingga posisi setiap baris membawa makna dan tidak dapat ditukar seperti baris data tabel biasa. Topik ini menjelaskan dua akibat langsung dari keunikan tersebut, yaitu keharusan pemisahan temporal pada saat evaluasi dan bahaya kebocoran data dari masa depan. Praktiknya menggunakan deret harian sintetis berpola mingguan yang dibangun di dalam skrip pemisahan-temporal.py, kemudian membandingkan hasil pemisahan acak dengan hasil pemisahan temporal pada model yang sama.
Urutan waktu membatasi cara membagi data#
Pemisahan acak memperlakukan setiap baris sebagai contoh yang saling bebas, sehingga baris mana pun boleh berpindah ke data latih atau data uji. Asumsi tersebut dapat diterima pada data tabel yang baris-barisnya memang tidak saling bergantung, misalnya catatan dimensi produk. Pada deret waktu, pengamatan hari ini menyimpan jejak pengamatan hari-hari sebelumnya, sehingga dua baris yang berdekatan waktunya membawa informasi yang hampir sama. Pemisahan acak menempatkan baris uji di tengah-tengah baris latih. Akibatnya, model dinilai pada tugas yang bukan tujuan utama peramalan, yaitu menebak kejadian yang tetangga waktunya sudah diketahui.
Diagram berikut membandingkan kedua cara membagi baris pada satu garis waktu. Pemisahan acak menaburkan data uji di antara data latih, sedangkan pemisahan temporal memotong garis waktu pada satu titik dan menitipkan seluruh baris sesudahnya sebagai data uji.
Konsekuensinya bersifat praktis, bukan sekadar teoretis. Skor evaluasi dari pemisahan acak mengukur kemampuan interpolasi, yaitu mengisi celah di antara kejadian yang sudah diketahui. Kebutuhan nyata model deret waktu justru ekstrapolasi, yaitu meramal periode yang sama sekali belum terjadi. Kedua tugas itu memiliki tingkat kesulitan yang berbeda, sehingga angka dari protokol yang keliru tidak dapat dipakai untuk memilih model peramalan.
Kebocoran informasi dari masa depan#
Keunikan yang kedua adalah mudahnya informasi masa depan menyusup ke tahap persiapan data, kejadian yang disebut kebocoran data (data leakage). Penyusupan ini terjadi ketika statistik atau fitur dihitung dari seluruh deret sebelum pemecahan dilakukan. Tiga contoh berikut paling sering muncul.
Pertama, rata-rata dan simpangan baku untuk penskalaan dihitung dari seluruh periode, termasuk periode yang nanti menjadi data uji. Kedua, batas normalisasi dihitung dengan melibatkan bulan-bulan terakhir deret. Ketiga, interpolasi dua arah mengisi nilai hilang di tengah deret dengan memakai nilai yang berada sesudah celah. Semua langkah itu berjalan tanpa satu pun pesan error karena sintaksnya memang benar.
Bentuk yang lebih halus adalah fitur yang tanpa sengaja membaca masa depan, misalnya rata-rata bergerak yang jendela hitungannya mencakup hari yang akan diprediksi. Fitur semacam itu membawa sebagian jawaban ke dalam masukan model, dan skor evaluasi naik tanpa tanda bahaya. Pemisahan temporal menutup jalur penyusupan yang paling besar karena seluruh baris uji selalu berada setelah baris latih. Meskipun begitu, tahap persiapan data tetap menuntut kewaspadaan.
Pemisahan temporal dengan TimeSeriesSplit#
Cara paling lugas menerapkan disiplin waktu adalah menyisihkan sebagian akhir deret sebagai data uji, misalnya 25 persen terakhir. scikit-learn juga menyediakan TimeSeriesSplit, yaitu pembagi yang meniru latihan peramalan berulang. Data latihnya dimulai dari sebagian kecil awal deret, kemudian bergeser maju lipat demi lipat, sedangkan setiap data uji selalu berada sesudah data latihnya. Skrip berikut membangun deret harian sintetis selama 150 hari, mengubahnya menjadi baris fitur berupa nilai lag, yaitu nilai deret pada tujuh hari sebelum hari target. Evaluasi pemisahan acak kemudian dibandingkan dengan evaluasi pemisahan temporal pada model pohon keputusan yang sama.
import numpy as np
from sklearn.metrics import mean_absolute_error
from sklearn.model_selection import TimeSeriesSplit, train_test_split
from sklearn.tree import DecisionTreeRegressor
# deret harian sintetis: tren + gelombang mingguan + derau kecil (seed tetap)
rng = np.random.default_rng(42)
n = 150
waktu = np.arange(n)
deret = 20 + 0.20 * waktu + 8.0 * np.sin(2 * np.pi * waktu / 7) + rng.normal(0, 0.5, n)
# fitur lag: tujuh nilai sebelum hari t menjadi penjelas bagi nilai hari t
lag = 7
X = np.zeros((n - lag, lag))
y = deret[lag:]
for i in range(n - lag):
X[i] = deret[i : i + lag][::-1]
print(f"Deret {n} titik, fitur lag {lag} hari, {X.shape[0]} baris fitur-target")
print()
# TimeSeriesSplit: latih selalu mendahului uji, lalu bergeser maju
tscv = TimeSeriesSplit(n_splits=4)
for no, (tr, te) in enumerate(tscv.split(X), 1):
print(f"Fold {no}: latih {len(tr):3d} baris (indeks {tr[0]}-{tr[-1]}), uji {len(te):2d} baris (indeks {te[0]}-{te[-1]})")
print()
# pemisahan acak: baris uji bercampur di tengah baris latih
Xa_latih, Xa_uji, ya_latih, ya_uji = train_test_split(
X, y, test_size=0.25, random_state=42
)
model = DecisionTreeRegressor(random_state=42).fit(Xa_latih, ya_latih)
mae_acak = mean_absolute_error(ya_uji, model.predict(Xa_uji))
# pemisahan temporal: 25 persen terakhir menjadi data uji
batas = int(0.75 * len(X))
model = DecisionTreeRegressor(random_state=42).fit(X[:batas], y[:batas])
mae_temporal = mean_absolute_error(y[batas:], model.predict(X[batas:]))
print(f"MAE pemisahan acak : {mae_acak:.3f}")
print(f"MAE pemisahan temporal: {mae_temporal:.3f}")Deret 150 titik, fitur lag 7 hari, 143 baris fitur-target
Fold 1: latih 31 baris (indeks 0-30), uji 28 baris (indeks 31-58)
Fold 2: latih 59 baris (indeks 0-58), uji 28 baris (indeks 59-86)
Fold 3: latih 87 baris (indeks 0-86), uji 28 baris (indeks 87-114)
Fold 4: latih 115 baris (indeks 0-114), uji 28 baris (indeks 115-142)
MAE pemisahan acak : 0.980
MAE pemisahan temporal: 2.175Daftar fold pada keluaran memperlihatkan mekanismenya. Data latih tumbuh dari 31 baris menjadi 115 baris, sedangkan setiap data uji selalu berada pada indeks yang lebih besar daripada seluruh data latihnya. Pembandingan MAE (mean absolute error) dilakukan pada model pohon keputusan yang kelemahannya mudah dijelaskan, yaitu hanya mampu mengulang pola di dalam rentang nilai data latihnya. Pada pemisahan acak, setiap baris uji memiliki tetangga waktu yang berdekatan di data latih, sehingga pohon cukup menyalin pola tetangga tersebut dan MAE-nya rendah di angka 0.980. Pada pemisahan temporal, pohon harus melampaui nilai terakhir data latih, sehingga MAE-nya naik menjadi 2.175, lebih dari dua kali lipat.
Selisih tersebut tidak berasal dari perbaikan model apa pun karena model dan datanya sama persis. Angka yang membaik murni berasal dari cara membagi baris, dan justru angka 0.980 itulah yang menipu. Ukuran kedua data uji sama-sama 36 baris, sehingga perbandingannya adil dari sisi jumlah contoh. Evaluasi deret waktu yang benar selalu membiarkan masa lalu melatih dan masa depan menilai. Perbedaan skor yang muncul dari protokol yang berbeda sebaiknya dibaca sebagai alarm, bukan sebagai kemajuan.
Kesalahan umum#
Kesalahan pada topik ini hampir selalu berupa pelanggaran disiplin waktu yang tidak menimbulkan pesan error.
- Menghitung statistik penskalaan atau rata-rata bergerak dari seluruh deret sebelum pemecahan. Kebiasaan ini dibawa dari data tabel yang barisnya saling bebas, padahal statistik tersebut menyimpan informasi periode uji. Statistik hanya dihitung dari data latih, kemudian diterapkan ke periode setelahnya, dan fitur seperti rata-rata bergerak dibangun hanya dari nilai masa lalu.
- Mengisi nilai hilang dengan interpolasi dua arah. Fungsi interpolasi mengisi celah di tengah deret dengan memakai nilai sesudah celah, sehingga masa depan ikut menentukan masa lalu. Pengisian dilakukan satu arah dengan nilai terakhir yang teramati, atau baris yang bolong dihilangkan.
- Membandingkan skor dari pemisahan acak dengan skor dari pemisahan temporal seolah-olah keduanya setara. Kedua angka memang berbentuk MAE yang sama, tetapi keduanya mengukur tugas yang berbeda. Seluruh perbandingan model pada data berwaktu memakai satu protokol temporal yang konsisten.
- Menyetel model berulang-ulang berdasarkan skor data uji temporal. Setiap keputusan yang diambil setelah melihat skor uji menyisipkan informasi uji ke dalam proses penyetelan, sehingga skor berikutnya terlalu optimistis dan data uji kehilangan perannya sebagai penilai akhir. Data uji hanya diperiksa setelah semua keputusan model selesai, sedangkan penyetelan memakai data validasi di dalam data latih.
Ringkasan#
- Baris deret waktu bergantung pada urutannya, sehingga pemisahan acak mengubah evaluasi menjadi interpolasi yang lebih mudah daripada peramalan sungguhan.
- Kebocoran dari masa depan masuk melalui statistik dan fitur yang dihitung dari seluruh deret, dan kejadiannya tidak pernah diawali pesan error.
TimeSeriesSplitmenahan semua baris uji agar selalu sesudah baris latih, dengan data latih yang tumbuh lipat demi lipat.- Pada eksperimen topik ini, MAE berubah dari 2.175 menjadi 0.980 semata karena cara membagi baris, bukan karena model membaik.
Referensi#
- sklearn.model_selection.TimeSeriesSplit — scikit-learn — Dokumentasi pembagi data berurutan yang dipakai pada skrip topik ini.
- Cross-validation: evaluating estimator performance — scikit-learn — Panduan resmi validasi silang yang memuat bagian khusus pemisahan data deret waktu.
- Common pitfalls and recommended practices — scikit-learn — Daftar jebakan evaluasi resmi, termasuk kebocoran data pada tahap pemisahan dan transformasi.