Belajar Machine Learning - Time Series Forecasting
Episode 12 of 25

Belajar Machine Learning - Time Series Forecasting

Memahami peramalan deret waktu: dekomposisi trend, seasonality, dan stationarity, rolling features dan backtesting yang jujur, lalu model ARIMA/SARIMA dan Prophet serta pendekatan ML modern dengan lag features dan LightGBM untuk forecasting penjualan.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Setelah di episode 11 kita membiasakan tracking dan reproducibility, pada episode ini kita menantang satu asumsi yang selama ini diam-diam kita pakai: data antar baris saling bebas. Untuk deret waktu — penjualan harian, harga saham, trafik server — asumsi itu salah total. Baris hari ini sangat bergantung pada hari kemarin.

Mengapa time series istimewa? Karena pengamatannya berurutan sepanjang waktu, sehingga split acak (seperti episode 4) adalah kesalahan fatal: data masa depan tidak boleh dipakai untuk melatih model yang memprediksi masa lalu. Inilah yang membuat evaluasi time series berbeda dari semuanya.

Komponen Deret Waktu

Sebuah deret waktu bisa diuraikan menjadi komponen:

100%
  • Trend: arah jangka panjang — penjualan naik 10% per tahun.
  • Seasonality: pola berulang dengan periode tetap — penjualan naik tiap Desember.
  • Noise: fluktuasi acak yang tidak bisa diprediksi.

Dekomposisi membantu kalian memahami struktur dan memilih model:

Dekomposisi deret waktu
import pandas as pd
from statsmodels.tsa.seasonal import seasonal_decompose
 
decomp = seasonal_decompose(
    df["penjualan"], model="additive", period=12
)
decomp.plot()

Stationarity

Deret dikatakan stasioner jika mean dan variance-nya konstan sepanjang waktu — tidak ada trend yang jelas. Kenapa penting? Karena model klasik seperti ARIMA mengasumsikan stasioneritas. Uji paling populer: Augmented Dickey-Fuller (ADF).

Uji stasioneritas
from statsmodels.tsa.stattools import adfuller
 
result = adfuller(df["penjualan"])
print(f"p-value = {result[1]:.4f}")
 
# p-value < 0.05 → deret stasioner
# p-value >= 0.05 → ada unit root, perlu differencing

Jika deret tidak stasioner, cara menstabilkannya: differencing — prediksi selisih antar observasi, bukan nilai absolutnya:

Differencing
df["penjualan_diff"] = df["penjualan"].diff()

Pendekatan ML modern (LightGBM dkk.) tidak terlalu peduli stasioneritas — mereka belajar dari fitur. Tapi memahami konsep ini tetap penting untuk interpretasi dan untuk model klasik.

Backtesting: Evaluasi yang Jujur

Ingat kembali larangan split acak. Untuk time series, evaluasi dilakukan berurutan:

100%

Setiap langkah, jendela training diperluas ke depan — meniru cara model bekerja di dunia nyata (memprediksi masa depan yang belum terlihat). Ini disebut walk-forward validation atau backtesting.

Walk-forward sederhana
import numpy as np
 
horizon = 3
errors = []
 
for step in range(horizon, len(series), horizon):
    train = series[:step]
    test = series[step:step + horizon]
 
    model.fit(train)
    preds = model.predict(test)
    errors.append(mean_absolute_error(test, preds))

Selalu gunakan metrik yang sama (MAE, RMSE) dan bandingkan dengan baseline naif — misalnya "prediksi = nilai kemarin" (persistence). Jika model kalian tidak mengalahkan baseline naif, ada yang salah.

Model Klasik: ARIMA, SARIMA, Prophet

ARIMA/SARIMA

ARIMA(p,d,q) memodelkan autokorelasi deret: p lag autoregressive, d differencing, q moving average. SARIMA menambahkan komponen musiman. Memilih p, d, q optimal bisa dibantu auto_arima:

ARIMA dengan auto_arima
from pmdarima import auto_arima
 
model = auto_arima(
    series, seasonal=True, m=12,
    stepwise=True, trace=False,
)
print(model.order)
forecast = model.predict(n_periods=12)

Kelebihan ARIMA: interpretatif dan kuat untuk data jangka pendek. Kelemahan: sulit menambahkan fitur eksternal (harga, promosi).

Prophet

Prophet (Meta) dirancang untuk deret waktu dengan musiman kuat dan outlier. Ia otomatis menangkap trend dan hari libur:

Prophet
from prophet import Prophet
 
df_prophet = df.rename(columns={"tanggal": "ds", "penjualan": "y"})
 
model = Prophet(weekly_seasonality=True)
model.fit(df_prophet)
 
future = model.make_future_dataframe(periods=90)
forecast = model.predict(future)
model.plot(forecast)

Prophet sangat ramah pengguna, tetapi kurang fleksibel dibanding ML murni untuk fitur eksternal.

Pendekatan ML: Lag Features + LightGBM

Untuk forecasting yang fleksibel dan akurat, pendekatan favorit di 2026 adalah mengubah time series menjadi masalah supervised tabular dengan lag features.

Membangun Lag Features

Lag & rolling features
df = df.sort_values("tanggal")
 
for lag in [1, 7, 14, 28]:
    df[f"lag_{lag}"] = df["penjualan"].shift(lag)
 
df["rolling_mean_7"] = df["penjualan"].rolling(7).mean()
df["rolling_std_7"] = df["penjualan"].rolling(7).std()
 
# Fitur kalender membantu menangkap seasonality
df["bulan"] = df["tanggal"].dt.month
df["hari"] = df["tanggal"].dt.dayofweek

Fitur lag_7 dan lag_28 menangkap pola mingguan dan bulanan; rolling_mean_7 menghaluskan noise. Inilah feature engineering yang kita pelajari di episode 9 — diterapkan pada waktu.

Training dengan Walk-Forward

Latih LightGBM dengan lag features
import lightgbm as lgb
 
df_ml = df.dropna()
X = df_ml.drop("penjualan", axis=1)
y = df_ml["penjualan"]
 
# Split berurutan, BUKAN acak!
cutoff = int(len(df_ml) * 0.8)
X_train, X_test = X.iloc[:cutoff], X.iloc[cutoff:]
y_train, y_test = y.iloc[:cutoff], y.iloc[cutoff:]
 
model = lgb.LGBMRegressor(random_state=42)
model.fit(X_train, y_train)
 
preds = model.predict(X_test)
print(f"MAE test = {mean_absolute_error(y_test, preds):.2f}")

Kesalahan paling umum pemula di sini: memakai train_test_split tanpa shuffle=False — data acak membuat fitur lag memakai nilai "masa depan", dan hasilnya menipu.

Praktik: Forecasting Penjualan

Urutan kerja yang disarankan untuk proyek forecasting:

  1. Plot deret dan dekomposisi untuk memahami trend/seasonality.
  2. Bangun baseline naif (persistence) sebagai patokan.
  3. Coba ARIMA/SARIMA untuk benchmark klasik.
  4. Bangun lag + rolling features dan latih LightGBM.
  5. Evaluasi dengan walk-forward dan bandingkan semua model.
Bandingkan dengan baseline
baseline_pred = X_test["lag_1"].values  # persistence
print(f"Baseline MAE = {mean_absolute_error(y_test, baseline_pred):.2f}")
print(f"LightGBM MAE = {mean_absolute_error(y_test, preds):.2f}")

Jika LightGBM kalah dari baseline, periksa kembali lag features — mungkin horizon prediksi terlalu jauh untuk lag yang dipakai.

Warning

Jangan pernah memakai metrik CV acak (cross_val_score default) untuk time series — ia mencampur masa depan ke masa lalu. Gunakan TimeSeriesSplit dari scikit-learn atau walk-forward manual seperti di atas.

Common Pitfalls

  • Split acak pada data berurutan — leakage masa depan, evaluasi menipu.
  • Lupa baseline naif — model "canggih" yang kalah dari "prediksi kemarin" adalah buang waktu.
  • Lag features yang bocor saat inference — pastikan fitur tersedia di waktu prediksi (tidak memakai nilai masa depan).
  • Mengabaikan seasonality — tanpa lag mingguan/bulanan, model tidak menangkap pola periodik.
  • Forecast horizon terlalu jauh — semakin jauh, semakin tidak akurat; kalibrasi ekspektasi.

Penutup

Pada episode 12 ini, kalian telah menguasai peramalan deret waktu.

Inti yang harus dibawa pulang:

  • Deret waktu = trend + seasonality + noise; butuh evaluasi berurutan.
  • Stationarity penting untuk model klasik (ARIMA); differencing menstabilkannya.
  • Backtesting/walk-forward menggantikan CV acak.
  • ARIMA/SARIMA dan Prophet untuk pendekatan klasik; lag + rolling features + LightGBM untuk ML modern.
  • Selalu kalahkan baseline naif.

Di episode 13 selanjutnya kita akan membahas NLP & text data dasar — dari tokenization, bag-of-words dan TF-IDF, hingga embeddings (word2vec/fastText) dan pipeline klasifikasi teks untuk spam detection dan sentiment analysis dengan scikit-learn. Sampai jumpa di episode 13!

Belajar Machine Learning - Time Series Forecasting | Belajar Machine Learning