SARIMA pada chapter sebelumnya menangkap musiman mingguan lewat orde musiman, tetapi jalur statistik bukan satu-satunya cara meramalkan. Pendekatan kedua memanfaatkan keahlian yang sudah kita bangun sejak awal Course ini: supervised learning. Triknya adalah reframing — jendela nilai lampau diubah menjadi kolom fitur, nilai berikutnya menjadi target, dan deret waktu pun berubah menjadi matriks tabular yang bisa dimakan regressor scikit-learn mana pun. Kita akan membangun matriks fitur lag dan fitur kalender untuk penjualan Toko Ceria, melatih RandomForestRegressor, lalu mengadunya secara jujur dengan baseline naive pada jendela uji yang sama dengan chapter ARIMA.
Dari Deret ke Matriks Supervised #
Mekanismenya sederhana: untuk setiap hari t, nilai y_(t-1) sampai y_(t-7) menjadi fitur dan y_t menjadi target. Operasi shift(k) pada pandas menggeser deret sebesar k langkah, sehingga kolom lag_k berisi nilai k hari sebelumnya yang sejajar dengan hari ini. Tujuh baris pertama tidak punya riwayat lengkap dan dibuang lewat dropna() — harga yang kita bayar untuk bentuk supervised ini. Di samping fitur lag, indeks tanggal menyumbang fitur kalender: kolom hari (0 = Senin sampai 6 = Minggu) dan akhir_pekan (1 untuk Sabtu–Minggu), keduanya diturunkan dari tanggal acuan tetap deret.
# Mengubah deret waktu menjadi matriks supervised: fitur lag dari nilai
# lampau plus fitur kalender (hari dan penanda akhir pekan).
import numpy as np
import pandas as pd
def buat_deret_penjualan(n_hari=168):
rng = np.random.default_rng(42)
idx = pd.date_range(start="2026-01-05", periods=n_hari, freq="D")
t = np.arange(n_hari)
tren = 120.0 + 0.45 * t
hari = np.asarray(idx.dayofweek)
efek_hari = np.select(
[hari == 4, hari == 5, hari == 6],
[10.0, 32.0, 40.0],
default=0.0,
)
promo = ((t >= 70) & (t < 84)).astype(float) * 45.0
kilat = (t == 105).astype(float) * 85.0
derau = rng.normal(0.0, 12.0, n_hari)
y = tren + efek_hari + promo + kilat + derau
return pd.Series(np.round(y, 1), index=idx, name="penjualan")
penjualan = buat_deret_penjualan()
df = pd.DataFrame({"y": penjualan})
for k in range(1, 8):
df[f"lag_{k}"] = penjualan.shift(k)
df["hari"] = penjualan.index.dayofweek # 0 = Senin ... 6 = Minggu
df["akhir_pekan"] = (penjualan.index.dayofweek >= 5).astype(int)
df = df.dropna()
fitur = [kolom for kolom in df.columns if kolom != "y"]
print(f"Bentuk matriks supervised: {df.shape[0]} baris x {df.shape[1]} kolom "
f"(1 target + {len(fitur)} fitur)")
def tampilkan(baris, judul):
print(f"\n{judul}:")
kepala = (" " + f"{'tanggal':10s} {'y':>6s} "
+ " ".join(f"{k:>7s}" for k in fitur if k.startswith("lag"))
+ f" {'hari':>4s} {'akhir_pekan':>11s}")
print(kepala)
for tanggal, baris_isi in baris.iterrows():
nilai = " ".join(f"{baris_isi[k]:7.1f}" for k in fitur if k.startswith("lag"))
print(f" {tanggal.date()} {baris_isi['y']:6.1f} {nilai} {int(baris_isi['hari']):4d} "
f"{int(baris_isi['akhir_pekan']):11d}")
tampilkan(df.iloc[:6], "Enam baris pertama")
tampilkan(df.loc["2026-04-17":"2026-04-21"], "Lima baris di sekitar hari diskon kilat")
print("\nKorelasi Pearson tiap fitur dengan target y:")
for kolom in fitur:
korelasi = np.corrcoef(df[kolom], df["y"])[0, 1]
print(f" {kolom:12s} {korelasi:+.3f}")Output yang Dihasilkan:
Bentuk matriks supervised: 161 baris x 10 kolom (1 target + 9 fitur)
Enam baris pertama:
tanggal y lag_1 lag_2 lag_3 lag_4 lag_5 lag_6 lag_7 hari akhir_pekan
2026-01-12 119.4 164.2 138.6 108.4 132.6 129.9 108.0 123.7 0 0
2026-01-13 123.4 119.4 164.2 138.6 108.4 132.6 129.9 108.0 1 0
2026-01-14 113.8 123.4 119.4 164.2 138.6 108.4 132.6 129.9 2 0
2026-01-15 135.1 113.8 123.4 119.4 164.2 138.6 108.4 132.6 3 0
2026-01-16 144.3 135.1 113.8 123.4 119.4 164.2 138.6 108.4 4 0
2026-01-17 158.2 144.3 135.1 113.8 123.4 119.4 164.2 138.6 5 1
Lima baris di sekitar hari diskon kilat:
tanggal y lag_1 lag_2 lag_3 lag_4 lag_5 lag_6 lag_7 hari akhir_pekan
2026-04-17 171.6 181.0 160.5 153.7 168.9 191.7 179.3 155.4 4 0
2026-04-18 207.2 171.6 181.0 160.5 153.7 168.9 191.7 179.3 5 1
2026-04-19 195.6 207.2 171.6 181.0 160.5 153.7 168.9 191.7 6 1
2026-04-20 249.8 195.6 207.2 171.6 181.0 160.5 153.7 168.9 0 0
2026-04-21 156.3 249.8 195.6 207.2 171.6 181.0 160.5 153.7 1 0
Korelasi Pearson tiap fitur dengan target y:
lag_1 +0.660
lag_2 +0.468
lag_3 +0.349
lag_4 +0.332
lag_5 +0.420
lag_6 +0.598
lag_7 +0.775
hari +0.473
akhir_pekan +0.543Mari kita amati hasil transformasi tersebut:
- Baris 28–34 (pembangunan matriks): perulangan
shift(k)membangun tujuh kolom lag, dua baris berikutnya menambahkan fitur kalender, dandropna()memangkas tujuh baris pertama — 168 hari menjadi 161 baris supervised. Daftarfiturdikumpulkan agar nama kolom tidak ditulis ulang saat melatih model. - Enam baris pertama (jendela yang bergeser): perhatikan diagonal nilai —
lag_1baris kedua berisiybaris pertama,lag_2baris ketiga berisiybaris pertama, dan seterusnya. Satu nilai penjualan hadir di delapan kolom yang berbeda sepanjang pergeseran jendelanya; inilah wujud ketergantungan temporal dalam format tabular. - Baris di sekitar 20 April (hari diskon kilat): target 249,8 pada 20 April berdiri di atas tujuh fitur lag yang seluruhnya normal (168–207). Tidak ada informasi di jendela lampau yang memberi tahu model tentang lonjakan tersebut — sebaliknya, baris 21 April membawa 249,8 sebagai
lag_1, dan model harus belajar bahwa lompatan sebesar itu biasanya tidak berlanjut. - Korelasi Pearson:
lag_7mencatat korelasi tertinggi (+0,775) — persis angka autokorelasi lag 7 pada Chapter Konsep Deret Waktu. Korelasilag_1(+0,660) dan penandaakhir_pekan(+0,543) menyusul. Urutan ini memberi dugaan awal fitur mana yang akan paling berpengaruh.
Melatih dan Mengevaluasi RandomForest #
Protokol evaluasi bab ini berbeda dari chapter ARIMA dan bedanya harus diucapkan secara eksplisit. Bab ini mengevaluasi prediksi satu langkah ke depan: ketika memprediksi hari t, nilai kemarin (lag_1) sudah diketahui karena berada di masa lalu. Chapter ARIMA meramalkan 14 hari sekaligus tanpa melihat satu pun nilai aktual di antaranya. Keduanya menjawab pertanyaan operasional yang berbeda — “berapa besok?” versus “berapa dua minggu ke depan?” — sehingga angka RMSE-nya tidak boleh dibandingkan mentah-mentah. Baseline pembandingnya pun menyesuaikan: naive versi satu-langkah cukup mengulang nilai kemarin.
# RandomForest pada fitur lag + kalender, dievaluasi satu langkah ke depan
# pada 14 hari terakhir dan dibandingkan dengan baseline naive (nilai kemarin).
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error
def buat_deret_penjualan(n_hari=168):
rng = np.random.default_rng(42)
idx = pd.date_range(start="2026-01-05", periods=n_hari, freq="D")
t = np.arange(n_hari)
tren = 120.0 + 0.45 * t
hari = np.asarray(idx.dayofweek)
efek_hari = np.select(
[hari == 4, hari == 5, hari == 6],
[10.0, 32.0, 40.0],
default=0.0,
)
promo = ((t >= 70) & (t < 84)).astype(float) * 45.0
kilat = (t == 105).astype(float) * 85.0
derau = rng.normal(0.0, 12.0, n_hari)
y = tren + efek_hari + promo + kilat + derau
return pd.Series(np.round(y, 1), index=idx, name="penjualan")
penjualan = buat_deret_penjualan()
df = pd.DataFrame({"y": penjualan})
for k in range(1, 8):
df[f"lag_{k}"] = penjualan.shift(k)
df["hari"] = penjualan.index.dayofweek
df["akhir_pekan"] = (penjualan.index.dayofweek >= 5).astype(int)
df = df.dropna()
fitur = [kolom for kolom in df.columns if kolom != "y"]
latih = df.iloc[:-14] # sampai 2026-06-07 (147 baris)
uji = df.iloc[-14:] # 2026-06-08 s.d. 2026-06-21
print(f"Data latih: {latih.index[0].date()} s.d. {latih.index[-1].date()} ({len(latih)} baris)")
print(f"Data uji : {uji.index[0].date()} s.d. {uji.index[-1].date()} ({len(uji)} baris)")
model = RandomForestRegressor(n_estimators=300, random_state=42, n_jobs=1)
model.fit(latih[fitur], latih["y"])
prediksi = model.predict(uji[fitur])
naive = uji["lag_1"] # prediksi satu-langkah: cukup ulang nilai kemarin
rmse_rf = np.sqrt(mean_squared_error(uji["y"], prediksi))
mae_rf = mean_absolute_error(uji["y"], prediksi)
rmse_naive = np.sqrt(mean_squared_error(uji["y"], naive))
mae_naive = mean_absolute_error(uji["y"], naive)
perbaikan = 100.0 * (rmse_naive - rmse_rf) / rmse_naive
print(f"\n{'model':28s} {'RMSE':>7s} {'MAE':>7s}")
print(f"{'RandomForest (lag + kalender)':28s} {rmse_rf:7.2f} {mae_rf:7.2f}")
print(f"{'naive (nilai kemarin)':28s} {rmse_naive:7.2f} {mae_naive:7.2f}")
print(f"Perbaikan RMSE RandomForest atas naive: {perbaikan:.1f}%")
print("\nPerbandingan aktual vs prediksi pada 14 hari data uji:")
for (tanggal, baris), pred in zip(uji.iterrows(), prediksi):
print(f" {tanggal.date()} aktual {baris['y']:6.1f} prediksi {pred:6.1f} "
f"naive {baris['lag_1']:6.1f}")
urutan = np.argsort(model.feature_importances_)[::-1]
print("\nKepentingan fitur (feature_importances_):")
for posisi in urutan:
print(f" {fitur[posisi]:12s} {model.feature_importances_[posisi]:.3f}")Output yang Dihasilkan:
Data latih: 2026-01-12 s.d. 2026-06-07 (147 baris)
Data uji : 2026-06-08 s.d. 2026-06-21 (14 baris)
model RMSE MAE
RandomForest (lag + kalender) 15.00 13.18
naive (nilai kemarin) 25.09 21.61
Perbaikan RMSE RandomForest atas naive: 40.2%
Perbandingan aktual vs prediksi pada 14 hari data uji:
2026-06-08 aktual 188.7 prediksi 193.7 naive 223.0
2026-06-09 aktual 211.0 prediksi 186.9 naive 188.7
2026-06-10 aktual 191.8 prediksi 198.0 naive 211.0
2026-06-11 aktual 202.4 prediksi 191.4 naive 191.8
2026-06-12 aktual 195.1 prediksi 203.9 naive 202.4
2026-06-13 aktual 209.3 prediksi 220.3 naive 195.1
2026-06-14 aktual 220.4 prediksi 227.5 naive 209.3
2026-06-15 aktual 183.7 prediksi 194.7 naive 220.4
2026-06-16 aktual 218.4 prediksi 191.7 naive 183.7
2026-06-17 aktual 183.5 prediksi 195.9 naive 218.4
2026-06-18 aktual 203.9 prediksi 188.7 naive 183.5
2026-06-19 aktual 193.4 prediksi 204.1 naive 203.9
2026-06-20 aktual 237.9 prediksi 210.7 naive 193.4
2026-06-21 aktual 239.8 prediksi 231.8 naive 237.9
Kepentingan fitur (feature_importances_):
lag_7 0.546
lag_1 0.194
lag_6 0.069
lag_2 0.044
hari 0.042
lag_5 0.032
lag_4 0.029
lag_3 0.026
akhir_pekan 0.019Bedah kode di atas menyoroti empat kelompok baris:
- Baris 30–41 (matriks + pemisahan): pembangunan matriks disalin dari contoh pertama agar skrip mandiri, lalu 14 baris terakhir dipisahkan sebagai uji — jendela waktu yang sama dengan chapter ARIMA.
RandomForestRegressormenerimarandom_state=42dann_jobs=1demi hasil yang dapat direproduksi persis. - Baris 43–52 (model dan baseline):
model.fit()memakan 147 baris latih,predict()menghasilkan prediksi untuk 14 hari uji, dan kolomlag_1langsung dipakai sebagai baseline naive satu-langkah. RMSE RandomForest 15,00 dan MAE 13,18 mengalahkan naive (25,09 dan 21,61) dengan perbaikan 40,2 persen — margin yang sehat, bukan kemenangan yang mencurigakan. - Baris 59–62 (prediksi harian): pada akhir pekan 13–14 Juni, model memprediksi 220,3 dan 227,5 — pola Sabtu–Minggu berhasil dipelajari dari fitur. Galat terbesar justru muncul pada hari kerja yang tak lazim seperti 9 Juni (aktual 211,0, prediksi 186,9): persistensi mingguan tidak selalu menutup fluktuasi harian.
- Baris 64–67 (kepentingan fitur):
lag_7menyerap lebih dari separuh kepentingan (0,546), disusullag_1(0,194) — urutan yang persis mengikuti korelasi pada contoh pertama. Fitur kalender menyumbang kecil (0,042 dan 0,019) karena informasi hari sudah sebagian besar tersirat dilag_7; keduanya tetap berguna saatlag_7berada jauh dari pola normalnya.
| Aspek | SARIMA (bab sebelumnya) | RandomForest fitur lag (bab ini) |
|---|---|---|
| Cara menangkap musiman | Orde musiman seasonal_order | Fitur lag_7 + fitur kalender |
| Fitur eksternal | Sulit (perlu exog yang sejajar) | Bebas: kolom apa pun (penanda kampanye, cuaca, hari besar) |
| Interpretabilitas | Koefisien + selang kepercayaan | Kepentingan fitur |
| Alat | statsmodels.tsa.arima.model | Regressor scikit-learn apa pun |
| Protokol alami | Multi-langkah | Satu-langkah (multi-langkah perlu rekursif) |
Dengan reframing ini, seluruh gudang alat supervised learning — dari regresi linear sampai gradient boosting — kini terbuka untuk data deret waktu. Namun satu pertanyaan evaluasi masih menggantung: seluruh angka pada bab ini berasal dari satu pemisahan latih-uji tunggal pada satu jendela waktu. Berapa kualitas model ini sepanjang musim lain — misalnya saat kampanye promo berlangsung dan model belum pernah melihatnya? Menjawabnya membutuhkan protokol evaluasi yang bergerak maju mengikuti waktu, dan itulah satu-satunya tema Chapter Validasi Walk-Forward.
Referensi Resmi #
- scikit-learn —
RandomForestRegressor— API resmi regressor hutan acak beserta parameterrandom_statedann_jobs. - scikit-learn — Ensemble methods: Forests — panduan resmi keluarga model berbasis pohon keputusan acak.
- pandas —
pandas.DataFrame.shift— API resmi operasi penggeser yang menjadi fondasi fitur lag.