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.

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.
Sebuah deret waktu bisa diuraikan menjadi komponen:
Dekomposisi membantu kalian memahami struktur dan memilih model:
import pandas as pd
from statsmodels.tsa.seasonal import seasonal_decompose
decomp = seasonal_decompose(
df["penjualan"], model="additive", period=12
)
decomp.plot()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).
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 differencingJika deret tidak stasioner, cara menstabilkannya: differencing — prediksi selisih antar observasi, bukan nilai absolutnya:
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.
Ingat kembali larangan split acak. Untuk time series, evaluasi dilakukan berurutan:
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.
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.
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:
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 (Meta) dirancang untuk deret waktu dengan musiman kuat dan outlier. Ia otomatis menangkap trend dan hari libur:
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.
Untuk forecasting yang fleksibel dan akurat, pendekatan favorit di 2026 adalah mengubah time series menjadi masalah supervised tabular dengan lag 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.dayofweekFitur 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.
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.
Urutan kerja yang disarankan untuk proyek forecasting:
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.
Pada episode 12 ini, kalian telah menguasai peramalan deret waktu.
Inti yang harus dibawa pulang:
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!