Model deret klasikal — exponential smoothing, ARIMA — memandang deret waktu sebagai satu utusan panjang yang harus dianalisis dengan persamaan khusus. Ada jalan berpikir yang sama sekali berbeda: hancurkan deret itu menjadi baris-baris tabel biasa, lalu serahkan pada model supervised learning yang sudah kita kuasai. Triknya ada pada pembuatan fitur — nilai lampau (lag) dan kalender — dan sesudah itu LinearRegression maupun tree ensemble bekerja seperti biasa. Pada chapter ini kita bangun tabel fitur dari penjualan Toko Kopi Kita, membandingkan tiga model pada periode uji yang hormat pada urutan waktu, dan mencatat kapan pendekatan ini unggul dibanding ARIMA.

Mengubah Deret Menjadi Masalah Supervised #

Peramalan pada dasarnya menjawab: “dengan informasi hari ini dan sebelumnya, berapa nilai besok?” Pertanyaan itu bisa ditata ulang menjadi tabel: setiap baris berisi fitur dari masa lalu dan satu target — nilai hari berjalan. Fitur paling penting dua keluarga. Pertama, fitur lag: salinan deret yang digeser 1, 2, …, 7 langkah — lag_7 berarti “nilai hari yang sama pekan lalu”. Kedua, fitur kalender: nomor hari dalam pekan, akhir pekan, tanggal merah — sinyal yang mengatur musiman.

Contoh barisnya pada data kita: hari Senin 13 Januari menjual 43 gelas; fiturnya adalah lag_1 = 55 (nilai Minggu 12 Januari), lag_7 = 41 (Senin 6 Januari), dan nomor_hari = 0 (Senin). Setelah shift() dan pembuangan baris kosong, deret 42 titik menjadi tabel 35 baris — harga yang harus dibayar: tujuh hari pertama kehilangan fitur lengkapnya.

Membangun Tabel Fitur #

Kita bangun tabel itu dengan pandas murni — shift() untuk lag, .index.dayofweek untuk kalender:

python
import numpy as np
import pandas as pd

rng = np.random.default_rng(42)
n = 42
tanggal = pd.date_range("2025-01-06", periods=n, freq="D")
bobot_hari = np.array([0, 1, 2, 3, 8, 14, 11], dtype=float)
gelas = 40 + 0.6 * np.arange(n) + bobot_hari[tanggal.dayofweek] + rng.normal(0, 4.0, n)
deret = pd.Series(gelas.round().astype(int), index=tanggal, name="gelas")

df = pd.DataFrame({"gelas": deret})
df["lag_1"] = df["gelas"].shift(1)
df["lag_7"] = df["gelas"].shift(7)
df["nomor_hari"] = df.index.dayofweek
df = df.dropna()
print(df[["lag_1", "lag_7", "nomor_hari", "gelas"]].head(6))

Output yang Dihasilkan:

text
            lag_1  lag_7  nomor_hari  gelas
2025-01-13   55.0   41.0           0     43
2025-01-14   43.0   37.0           1     46
2025-01-15   46.0   46.0           2     44
2025-01-16   44.0   49.0           3     53
2025-01-17   53.0   43.0           4     58
2025-01-18   58.0   52.0           5     61

Mari kita telaah hasilnya:

  1. df["gelas"].shift(1) menggeser kolom target satu langkah — baris 13 Januari kini menyimpan nilai 12 Januari sebagai fitur lag_1; shift(7) menyimpan nilai hari yang sama pekan sebelumnya sebagai lag_7.
  2. df.index.dayofweek membaca kalender langsung dari indeks: 0 Senin sampai 6 Minggu — inilah fitur yang membuat model “tahu” bahwa Sabtu berbeda dari Senin tanpa perlu menunggu pola tersirat dari lag.
  3. dropna() membuang tujuh baris pertama yang lag-nya belum terisi (baris 6-12 Januari), menyisakan 35 baris data latih-uji yang lengkap.
  4. Perhatikan baris 13 Januari: lag_7 = 41 berasal dari Senin 6 Januari, dan nomor_hari = 0 — keduanya membawa informasi “ini Senin” dari dua arah, nilai dan kalender.

Melatih dan Membandingkan Tiga Model #

Pembagian data latih-uji tetap menghormati waktu: 28 baris pertama untuk latih, 7 baris terakhir untuk uji — tanpa pengacakan. Kita adu tiga model bertingkat kompleksitas: LinearRegression sebagai pembanding sederhana, RandomForestRegressor, dan GradientBoostingRegressor — semuanya dengan random_state=42.

python
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor

rng = np.random.default_rng(42)
n = 42
tanggal = pd.date_range("2025-01-06", periods=n, freq="D")
bobot_hari = np.array([0, 1, 2, 3, 8, 14, 11], dtype=float)
gelas = 40 + 0.6 * np.arange(n) + bobot_hari[tanggal.dayofweek] + rng.normal(0, 4.0, n)
deret = pd.Series(gelas.round().astype(int), index=tanggal, name="gelas")

df = pd.DataFrame({"gelas": deret})
df["lag_1"] = df["gelas"].shift(1)
df["lag_7"] = df["gelas"].shift(7)
df["nomor_hari"] = df.index.dayofweek
df = df.dropna()

fitur = ["lag_1", "lag_7", "nomor_hari"]
batas = 28
latih = df.iloc[:batas]
ujian = df.iloc[batas:]

model_lr = LinearRegression().fit(latih[fitur], latih["gelas"])
model_rf = RandomForestRegressor(n_estimators=100, random_state=42).fit(latih[fitur], latih["gelas"])
model_gb = GradientBoostingRegressor(n_estimators=100, random_state=42).fit(latih[fitur], latih["gelas"])

for nama, model in [
    ("LinearRegression", model_lr),
    ("RandomForestRegressor", model_rf),
    ("GradientBoostingRegressor", model_gb),
]:
    prediksi = model.predict(ujian[fitur])
    mae = np.abs(prediksi - ujian["gelas"]).mean()
    print(f"{nama:24s} MAE = {mae:.2f} gelas")

Output yang Dihasilkan:

text
LinearRegression         MAE = 4.01 gelas
RandomForestRegressor    MAE = 4.49 gelas
GradientBoostingRegressor MAE = 4.84 gelas

Mari kita baca hasilnya:

  1. latih = df.iloc[:28] mengambil masa lalu untuk berlatih dan ujian = df.iloc[28:] menyimpan masa depan untuk diuji — pembagian berbasis posisi tanpa shuffle, persis kerangka time series split yang akan kita dalami dua chapter lagi.
  2. LinearRegression menang (MAE 4,01), mengungguli kedua tree ensemble. Dengan hanya 28 baris latih dan 3 fitur, data kita terlalu miskin untuk memberi makan model yang lebih lentur — tree justru menemukan pola-pola kecil dari noise (bentuk ringan overfitting). Pelajarannya penting: pada data deret kecil, model sederhana sering menjadi pilihan paling kuat.
  3. MAE 4,01 gelas bersaing sehat dengan Holt-Winters (3,06) dari chapter Exponential Smoothing — padahal LinearRegression hanya diberi tiga fitur. Dengan menambah fitur cuaca, promo, atau libur, pendekatan tabel ini punya ruang tumbuh yang tidak dimiliki model deret klasikal.

Kapan Machine Learning, Kapan ARIMA #

SituasiPilihan alamiAlasan
Hanya satu variabel, data pendek (< 100 titik)Exponential smoothing / ARIMAModel parametrik hemat data
Ada fitur eksternal (cuaca, promo, kalender libur)ML dengan fitur lagModel klasikal susah menampung regressor bebas
Perlu interpretasi koefisien dan interval bawaanARIMAInterval kepercayaan turut langsung dari teori
Pola non-linear (efek ambang, interaksi kompleks)Tree ensembleLinearitas ARIMA/ES menjadi pembatas
Banyak deret serupa sekaligus (tiap produk, tiap kota)ML global atau model terpisah per deretTergantung volume data tiap deret

Pilihan bukan perkara mode, melainkan bentuk data dan pertanyaan. Kerangka fitur-lag menjadikan forecasting anggota keluarga supervised learning biasa — dengan segala peralatan evaluasi yang sudah kita punya — dan itulah yang perlu kita luruskan selanjutnya: mengukur mutu ramalan dengan benar dan jujur.

Rangkuman #

  • Forecasting bisa ditata ulang menjadi supervised learning: fitur lag (shift) + fitur kalender (dayofweek) dengan target nilai berjalan; 42 titik menjadi 35 baris setelah dropna().
  • Pembagian latih-uji harus mengikuti urutan waktu tanpa shuffle; fitur hanya boleh memuat informasi masa lalu.
  • Pada 28 baris latih, LinearRegression (MAE 4,01) mengalahkan RandomForest dan GradientBoosting — data kecil memenangkan model sederhana.
  • ML unggul saat tersedia fitur eksternal dan pola non-linear; ARIMA unggul saat data pendek, satu variabel, dan dibutuhkan interval dari teori.

Angka MAE 4,01 di atas baru satu percobaan pada satu jendela uji. Seberapa jauh angka itu bisa dipercaya? Bab berikutnya menyusun pengukuran yang adil untuk ramalan: metrik MAE, RMSE, MAPE, dan pembanding wajib yang murah namun memalukan bila dikalahkan — naive baseline.

Referensi #

  1. scikit-learn — LinearRegression
  2. scikit-learn — Model Evaluation