Sepanjang Section ini kita sudah memproduksi ramalan dari banyak mesin: SES dan Holt-Winters, ARIMA, sampai LinearRegression berfitur lag. Angka MAE 3,06 untuk Holt-Winters dan 4,01 untuk LinearRegression sudah pernah kita sebut, tetapi keduanya lahir dari hitungan yang berbeda-beda dan belum pernah berdiri di podium yang sama. Pertanyaan paling mendasar dalam evaluasi forecasting pun bukan “berapa besar galatnya”, melainkan “galat sebesar itu bagus dibandingkan dengan apa”. Pada chapter ini kita rapikan dua perkakas evaluasi sekaligus: tiga metrik kesalahan yang membaca galat dari sudut berbeda, dan naive baseline — peramal paling murah sedunia yang wajib dikalahkan sebelum model mana pun layak dipakai.
Tiga Metrik untuk Membaca Galat #
Segalanya berawal dari satu besaran bernama galat: selisih antara nilai yang diramal dan nilai yang benar-benar terjadi, e_t = ramalan_t - aktual_t. Metrik hanyalah cara merangkum banyak galat menjadi satu angka, dan tiap cara merangkum menyorot sisi yang berbeda. Tiga yang paling sering dipakai di dunia forecasting:
MAE = mean(|e|) rata-rata galat dalam nilai mutlak
RMSE = sqrt(mean(e^2)) akar dari rata-rata galat dikuadratkan
MAPE = mean(|e| / aktual) x 100 rata-rata galat relatif dalam persen| Metrik | Satuan hasil | Yang ditonjolkan | Yang perlu diwaspadai |
|---|---|---|---|
| MAE | sama dengan data (gelas) | kesalahan “rata-rata”, mudah dikomunikasikan | kesalahan besar dan kecil dihitung sama |
| RMSE | sama dengan data (gelas) | menghukum kesalahan besar lebih berat | satu hari buruk bisa mendominasi angka |
| MAPE | persen | bisa dibandingkan lintas produk berskala beda | bermasalah bila nilai aktual dekat nol |
Kita bedah mekanisme ketiganya pada lima hari data mainan — hasil catatan kasir sebuah kedai:
import numpy as np
aktual = np.array([52, 48, 61, 45, 58])
ramalan = np.array([50, 50, 55, 50, 56])
galat = ramalan - aktual
print("galat (ramalan - aktual):", galat)
mae = np.mean(np.abs(galat))
rmse = np.sqrt(np.mean(galat**2))
mape = np.mean(np.abs(galat) / aktual) * 100
print(f"MAE = {mae:.2f} gelas")
print(f"RMSE = {rmse:.2f} gelas")
print(f"MAPE = {mape:.2f}%")Output yang Dihasilkan:
galat (ramalan - aktual): [-2 2 -6 5 -2]
MAE = 3.40 gelas
RMSE = 3.82 gelas
MAPE = 6.48%Mari kita telaah baris-baris kode di atas:
galat = ramalan - aktualmenghasilkan lima selisih bertanda: ramalan kelebihan 2 gelas pada hari pertama (-2) dan kekurangan 6 gelas pada hari ketiga (-6). Tanda menunjukkan arah meleset; tandanya dibuang saat dirangkum agar kelebihan dan kekurangan tidak saling meniadakan.mae = np.mean(np.abs(galat))merata-ratakan nilai mutlak: (2 + 2 + 6 + 5 + 2) / 5 = 3,4 gelas — “rata-rata kita meleset 3,4 gelas”, kalimat yang langsung dipahami pemilik kedai.rmse = np.sqrt(np.mean(galat**2))mengkuadratkan sebelum dirata-ratakan: (4 + 4 + 36 + 25 + 4) / 5 = 14,6, lalu diakarkan menjadi 3,82. Kuadrat membuat kesalahan 6 gelas menyumbang jauh lebih besar daripada kesalahan 2 gelas.mape = np.mean(np.abs(galat) / aktual) * 100membagi tiap galat dengan aktualnya: meleset 6 gelas pada hari dengan aktual 61 dihitung lebih ringan (9,8%) daripada meleset 5 gelas pada aktual 45 (11,1%) — MAPE bicara dalam skala relatif.
Naive Baseline: Pembanding yang Wajib #
Metrik menjawab “seberapa jauh ramalan meleset”, tetapi belum menjawab “apakah itu cukup baik”. MAE 4 gelas patut dibanggakan bila deret biasanya berayun 40 gelas sehari, dan patut dipertanyakan bila deret nyaris datar. Rujukan yang adil datang dari peramal termurah yang bisa dibangun tanpa model apa pun — naive baseline. Dua varian yang paling dipakai:
| Baseline | Ramalan untuk besok | Asumsi tersembunyi | Cocok saat |
|---|---|---|---|
| Naive (nilai terakhir) | sama dengan nilai hari ini | deret berjalan acak di sekitar level terkini | perubahan harian tak berpola |
| Naive musiman (lag 7) | nilai hari yang sama pekan lalu | pola mingguan berulang antar pekan | ada musiman mingguan yang kuat |
Kekuatan baseline justru letaknya: ia gratis, tidak bisa overfitting, dan membangunnya hanya butuh satu baris pandas. Karena itu pula ia menjadi lantai yang sah — model yang berbayar kompleksitas wajib melompatinya.
Empat Peramal pada Satu Pekan Uji #
Saatnya pertandingan yang jujur. Kita pakai kembali deret 42 hari Toko Kopi Kita (tren naik + musiman mingguan + derau, seed 42), melatih semuanya pada 35 hari pertama, lalu mengujinya pada pekan 10-16 Februari yang sama. Empat peserta: dua naive baseline, Holt-Winters mewakili keluarga exponential smoothing, dan LinearRegression berfitur lag mewakili jalan machine learning — dua model terakhir hanya boleh melihat masa lalu.
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from statsmodels.tsa.holtwinters import ExponentialSmoothing
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")
latih = deret.iloc[:-7]
ujian = deret.iloc[-7:]
aktual = ujian.values
def mae(ramalan):
return np.mean(np.abs(np.asarray(ramalan) - aktual))
def rmse(ramalan):
return np.sqrt(np.mean((np.asarray(ramalan) - aktual) ** 2))
def mape(ramalan):
return np.mean(np.abs((np.asarray(ramalan) - aktual) / aktual)) * 100
naive_akhir = np.full(7, latih.iloc[-1])
naive_musiman = latih.iloc[-7:].values
hw = ExponentialSmoothing(
latih, trend="add", seasonal="add", seasonal_periods=7,
initialization_method="estimated",
).fit()
ramalan_hw = hw.forecast(7)
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"]
model_lr = LinearRegression().fit(df.iloc[:28][fitur], df.iloc[:28]["gelas"])
ramalan_lr = model_lr.predict(df.iloc[28:][fitur])
per_hari = pd.DataFrame(
{
"aktual": aktual,
"naive_akhir": naive_akhir,
"naive_musiman": naive_musiman,
"holt_winters": ramalan_hw.round(1).values,
"linreg_lag": np.round(ramalan_lr, 1),
},
index=ujian.index,
)
print(per_hari)
print()
hasil = pd.DataFrame(
{
"MAE": [mae(naive_akhir), mae(naive_musiman), mae(ramalan_hw), mae(ramalan_lr)],
"RMSE": [rmse(naive_akhir), rmse(naive_musiman), rmse(ramalan_hw), rmse(ramalan_lr)],
"MAPE": [mape(naive_akhir), mape(naive_musiman), mape(ramalan_hw), mape(ramalan_lr)],
},
index=["Naive nilai terakhir", "Naive musiman (lag 7)",
"Holt-Winters", "LinearRegression (lag)"],
).round(2)
print(hasil)Output yang Dihasilkan:
aktual naive_akhir naive_musiman holt_winters linreg_lag
2025-02-10 66 74 58 62.2 60.6
2025-02-11 62 74 60 63.6 61.2
2025-02-12 61 74 69 67.2 66.2
2025-02-13 63 74 60 67.2 62.4
2025-02-14 74 74 65 71.8 66.6
2025-02-15 81 74 71 78.0 73.1
2025-02-16 78 74 74 78.4 77.3
MAE RMSE MAPE
Naive nilai terakhir 7.86 8.97 12.00
Naive musiman (lag 7) 6.29 6.95 8.98
Holt-Winters 3.06 3.53 4.63
LinearRegression (lag) 4.01 5.00 5.67Mari kita baca hasilnya:
naive_akhir = np.full(7, latih.iloc[-1])mengulang nilai hari terakhir data latih (Minggu 9 Februari, 74 gelas) untuk tujuh hari ke depan. Kolomnya datar di angka 74 sementara aktual berayun 61-81 — MAE 7,86 adalah harga dari mengabaikan irama mingguan.naive_musiman = latih.iloc[-7:].valuesmengambil nilai pekan terakhir data latih: Senin 3 Februari (58) meramal Senin 10 Februari, dan seterusnya. Bentuk mingguannya benar, tetapi ramalannya rendah sistematis sekitar 4-5 gelas karena tren menaikkan level deret kira-kira 0,6 x 7 = 4 gelas per pekan.- Holt-Winters menang (MAE 3,06) karena satu-satunya peserta yang memodelkan level, tren, dan musiman sekaligus; LinearRegression (4,01) menyusul dengan modal tiga fitur lag dan kalender.
- MAPE memindahkan semuanya ke skala persen: 12,00 / 8,98 / 4,63 / 5,67 — kedua model memangkas kesalahan relatif menjadi kurang dari separuh milik baseline.
- Rasio RMSE terhadap MAE milik LinearRegression (5,00/4,01 = 1,25) paling tinggi — kesalahannya paling tidak merata, dengan hari-hari seperti Sabtu (73,1 vs aktual 81) yang jauh meleset.
Kapan Model Kalah dari Baseline #
Kalah dari baseline bukan kejadian langka yang memalukan; ia terjadi dengan pola yang bisa dipelajari. Tiga situasi paling umum:
| Situasi | Yang biasanya unggul | Alasannya |
|---|---|---|
| Musiman kuat, model tanpa komponen musiman | Naive musiman | baseline menangkap irama mingguan secara gratis; SES kita yang meramal datar (MAE 6,75 pada chapter Exponential Smoothing) kalah dari naive musiman 6,29 |
| Data latih sangat sedikit | Baseline atau model paling sederhana | model lentur menemukan pola dari derau; RandomForest dan GradientBoosting (4,49 dan 4,84) kalah dari LinearRegression (4,01) pada 28 baris latih |
| Terjadi perubahan struktural di periode uji | Tidak ada pemenang sejati | semua peserta mewarisi dunia lama; kasus ini kita bedah tuntas pada chapter Keterbatasan Prediksi Deret Waktu |
Implikasinya dua arah. Bila model kalah dari baseline, jangan buru-buru mengganti dengan model yang lebih rumit — periksa dulu apakah komponen kunci seperti musiman sudah masuk, lalu tambahkan fitur yang membawa informasi baru. Untuk pendalaman MAE dan RMSE di luar konteks waktu, chapter Metrik Evaluasi Regresi membahasnya lengkap.
Rangkuman #
- Galat
e_t = ramalan_t - aktual_tadalah bahan baku semua metrik; MAE merata-rata nilai mutlaknya, RMSE menghukum kesalahan besar lewat kuadrat, MAPE memindahkan galat ke skala persen relatif terhadap aktual. - RMSE selalu berada di atas MAE kecuali semua galat sama besar; jarak keduanya mengukur ketidakmerataan kesalahan.
- Naive baseline — nilai terakhir dan naive musiman (lag 7) — adalah lantai wajib: Holt-Winters (3,06) dan LinearRegression (4,01) melampauinya dengan jelas, sementara SES datar (6,75) justru kalah dari naive musiman (6,29).
- Evaluasi yang jujur selalu menaruh model dan baseline pada periode uji yang sama, dengan metrik yang dibaca berdampingan.
Masih ada yang mengganjal dari hasil di atas: seluruh angka lahir dari satu jendela uji tujuh hari. Pekan yang kebetulan tenang bisa membuat model tampak hebat, dan pekan yang bergejolak bisa membuatnya tampak buruk — satu angka dari satu percobaan belum bisa dipercaya. Bagaimana kita mengulang evaluasi dengan disiplin tanpa melanggar urutan waktu adalah materi chapter Validasi Deret Waktu .