Mesin prediksi tidak memakan deret mentah; mesin itu memakan tabel. Pada bagian ini kita mengubah 36 baris deret waktu menjadi tabel fitur bergaya supervised learning: setiap baris memuat informasi masa lalu sebagai feature dan harga minggu berjalan sebagai target. Dua keputusan menunggu di sini dan keduanya menentukan kejujuran seluruh proyek: fitur apa yang boleh dipakai, dan bagaimana data dibagi tanpa membocorkan masa depan.
Dari Deret menjadi Tabel Fitur #
Ide dasarnya adalah jendela geser. Untuk memprediksi harga minggu ke-t, kita membuka jendela ke belakang: harga minggu t−1, harga minggu t−2, dan posisi kalender t itu sendiri. Deret digeser satu dan dua langkah dengan shift(), lalu setiap baris berdiri sendiri sebagai contoh latih:
| Baris fitur | lag_1 (harga W−1) | lag_2 (harga W−2) | minggu_ke | target (harga W) |
|---|---|---|---|---|
| W3 | 40,1 (W2) | 40,1 (W1) | 3 | 40,1 |
| W4 | 40,1 (W3) | 40,1 (W2) | 4 | 38,8 |
| W5 | 38,8 (W4) | 40,1 (W3) | 5 | 36,4 |
Blok berikut membangun tabel itu dari data bersih:
import pandas as pd
# Resep pembersihan hasil Bagian 4: duplikat dibuang, '-' diinterpolasi.
df = pd.read_csv("../data/harga_mingguan.csv")
bersih = df.drop_duplicates(subset=["minggu"], keep="first").copy()
bersih["harga_ribu"] = pd.to_numeric(bersih["harga_ribu"], errors="coerce")
bersih = bersih.sort_values("tanggal_mulai").reset_index(drop=True)
bersih["harga_ribu"] = bersih["harga_ribu"].interpolate().round(1)
# Jendela geser: setiap baris membawa harga 1-2 minggu sebelumnya sebagai fitur.
fitur = pd.DataFrame(
{
"lag_1": bersih["harga_ribu"].shift(1),
"lag_2": bersih["harga_ribu"].shift(2),
"minggu_ke": bersih["harga_ribu"].index + 1,
"target": bersih["harga_ribu"],
"minggu": bersih["minggu"],
}
).dropna().reset_index(drop=True)
fitur["minggu_ke"] = fitur["minggu_ke"].astype(int)
print(f"Deret 36 baris menjadi tabel fitur {len(fitur)} baris (W1 dan W2 tak punya riwayat penuh).")
print()
print(fitur.head(6).to_string(index=False))
print()
print(fitur.tail(3).to_string(index=False))Output yang Dihasilkan:
Deret 36 baris menjadi tabel fitur 34 baris (W1 dan W2 tak punya riwayat penuh).
lag_1 lag_2 minggu_ke target minggu
40.1 40.1 3 40.1 W3
40.1 40.1 4 38.8 W4
38.8 40.1 5 36.4 W5
36.4 38.8 6 35.9 W6
35.9 36.4 7 37.2 W7
37.2 35.9 8 38.6 W8
lag_1 lag_2 minggu_ke target minggu
42.5 40.2 34 43.0 W34
43.0 42.5 35 41.9 W35
41.9 43.0 36 40.9 W36Mari kita telaah hasilnya:
- 34 baris, bukan 36:
shift(1)danshift(2)mengisi dua baris pertama denganNaNkarena W1 dan W2 belum punya riwayat dua minggu penuh;dropna()membuangnya. Harga bayaran ini wajar dan jauh lebih jujur daripada mengarang nilai lag untuk minggu paling awal. - Setiap baris kini mandiri: baris W4 memuat
lag_140,1 (harga W3) danlag_240,1 (harga W2) — kebetulan sama karena tiga minggu pertama memang bertahan di 40,1. Model membaca baris ini seperti membaca baris pelanggan pada data tabular biasa. - Kolom
minggudipisahkan dari fitur: kolom itu hanya penanda; ia ikut dibawa agar tabel mudah dibaca manusia, bukan untuk dipelajari model.
Tiga Fitur dan Perannya #
Pilihan fitur mengikuti temuan Bagian 5. lag_1 membawa level terkini deret — prediktor terkuat sebagaimana ditunjukkan korelasi lag-1 0,728. lag_2 memperluas memori: bersama lag_1, keduanya memungkinkan model membaca arah pergerakan (harga sedang menanjak atau melandai), bukan hanya posisinya. minggu_ke adalah bilangan bulat 3–36 yang tumbuh satu tiap minggu — kendaraan sederhana untuk tren +0,089 ribu per minggu yang sudah kita ukur.
Mengapa berhenti di dua lag? Dengan hanya 34 baris contoh, setiap fitur tambahan memakan derajat kebebasan dan membuka pintu overfitting — model hafal derau, bukan pola. Prinsip hemat ini kita pegang sampai akhir proyek: fitur sedikit yang bisa dijelaskan lebih berharga daripada fitur banyak yang buram.
Bahaya Pembagian Acak: Kebocoran Masa Depan #
Tabel fitur kini tampak seperti data tabular biasa — dan di sinilah jebakannya. Kebiasaan standar pada data tabular adalah train_test_split acak. Blok berikut mencobanya sekadar untuk melihat akibatnya:
import pandas as pd
from sklearn.model_selection import train_test_split
# Resep pembersihan hasil Bagian 4: duplikat dibuang, '-' diinterpolasi.
df = pd.read_csv("../data/harga_mingguan.csv")
bersih = df.drop_duplicates(subset=["minggu"], keep="first").copy()
bersih["harga_ribu"] = pd.to_numeric(bersih["harga_ribu"], errors="coerce")
bersih = bersih.sort_values("tanggal_mulai").reset_index(drop=True)
bersih["harga_ribu"] = bersih["harga_ribu"].interpolate().round(1)
fitur = pd.DataFrame(
{
"lag_1": bersih["harga_ribu"].shift(1),
"lag_2": bersih["harga_ribu"].shift(2),
"minggu_ke": bersih["harga_ribu"].index + 1,
"target": bersih["harga_ribu"],
"minggu": bersih["minggu"],
}
).dropna().reset_index(drop=True)
fitur["minggu_ke"] = fitur["minggu_ke"].astype(int)
# Pembanding 1: pembagian ACAK seperti pada data tabular biasa.
latih_acak, uji_acak = train_test_split(fitur, test_size=6, random_state=42)
print("Pembagian acak (random_state=42):")
print(" data uji :", sorted(uji_acak["minggu"].tolist()))
print(f" data latih : {len(latih_acak)} minggu tersebar dari W{latih_acak['minggu_ke'].min()} sampai W{latih_acak['minggu_ke'].max()}")
print(f" ada minggu latih yang lebih baru daripada seluruh minggu uji : {bool((latih_acak['minggu_ke'] > uji_acak['minggu_ke'].max()).any())}")
print()
# Pembanding 2: pembagian KRONOLOGIS - 6 minggu terakhir menguji.
latih = fitur.iloc[:-6].copy()
uji = fitur.iloc[-6:].copy()
print("Pembagian kronologis:")
print(f" data latih : W{latih['minggu_ke'].min()} sampai W{latih['minggu_ke'].max()} ({len(latih)} baris)")
print(f" data uji : W{uji['minggu_ke'].min()} sampai W{uji['minggu_ke'].max()} ({len(uji)} baris)")
print(f" data latih berhenti tepat sebelum data uji dimulai : {latih['minggu_ke'].max() < uji['minggu_ke'].min()}")Output yang Dihasilkan:
Pembagian acak (random_state=42):
data uji : ['W11', 'W18', 'W22', 'W27', 'W29', 'W30']
data latih : 28 minggu tersebar dari W3 sampai W36
ada minggu latih yang lebih baru daripada seluruh minggu uji : True
Pembagian kronologis:
data latih : W3 sampai W30 (28 baris)
data uji : W31 sampai W36 (6 baris)
data latih berhenti tepat sebelum data uji dimulai : TruePembagian acak menjadikan W11 dan W18 sebagai data uji, sementara W31 sampai W36 — minggu-minggu yang lebih baru — justru dipakai melatih model. Model yang diuji seperti ini tidak memprediksi masa depan; ia menjawab soal yang jawabannya sudah pernah dilihatnya. Skor evaluasinya pasti indah, dan pasti menipu. Kebocoran seperti ini disebut data leakage: informasi dari masa uji merembes ke masa latih.
Pembagian Kronologis 28/6 #
Pembagian yang jujur memotong deret pada satu titik waktu: 28 baris fitur pertama (W3–W30) untuk melatih, 6 baris terakhir (W31–W36) untuk menguji. Dari 36 minggu data, 30 minggu pertama menyumbang baris latih — dua minggu awal hilang sebagai harga shift. Rasio ini memang miring ke data latih, tetapi enam minggu uji cukup untuk satu laporan jujur pertama, dan setiap minggu uji benar-benar tak tersentuh model sebelum diuji. Prinsip membagi menurut waktu — beserta varian time series split untuk evaluasi berlapis — dibahas tuntas pada chapter Validasi Deret Waktu
.
Ringkasan Bagian 6 #
Deret 36 baris telah menjadi tabel fitur 34 baris: lag_1 dan lag_2 membawa harga dua minggu terakhir, minggu_ke membawa tren. Pembagian acak terbukti membocorkan masa depan ke data latih, sehingga kita memakai pembagian kronologis — 28 baris latih (W3–W30) dan 6 baris uji (W31–W36).
Dengan tabel dan aturan main yang jujur ini, kita perlu satu patokan sebelum melatih model apa pun. Patokan itu — prakiraan naive — dibangun pada Bagian 7: Membangun Baseline .