Model sudah dilatih dan koefisiennya sudah dibaca; kini tibalah saat yang menentukan: enam minggu yang belum pernah dilihat model sama sekali. Evaluasi pada deret waktu hanya jujur bila data uji berada sepenuhnya setelah data latih, dan pembagian kronologis 28/6 dari Bagian 6 menjamin hal itu. Pada bagian ini kita menjajarkan prakiraan model dan baseline naive minggu demi minggu pada W31–W36, meringkasnya menjadi MAE dan RMSE, lalu membaca hasilnya tanpa dibuat-buat: seberapa jauh model mengalahkan garis start dari Bagian 7, dan pada minggu seperti apa keunggulan itu sesungguhnya lahir.
Pertandingan pada Enam Minggu Uji #
Aturan mainnya sederhana. Untuk setiap minggu uji, model menerima lag_1, lag_2, dan minggu_ke lalu mengeluarkan satu angka prakiraan; baseline naive cukup memakai lag_1 sebagai jawabannya. Enam minggu W31–W36 sengaja memuat dua medan sekaligus — tanjakan W31–W34 tempat harga menanjak dari 39,1 ke 43,0, lalu lereng W35–W36 tempat harga melandai kembali — sehingga kemampuan kedua metode mengikuti arah pergerakan ikut teruji:
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
# Resep pembersihan hasil Bagian 4: duplikat dibuang, '-' diinterpolasi.
df = pd.read_csv("../data/harga_mingguan.csv")
bersih = df.drop_duplicates(subset=["minggu"], keep="first").copy()
bersih["harga_ribu"] = pd.to_numeric(bersih["harga_ribu"], errors="coerce")
bersih = bersih.sort_values("tanggal_mulai").reset_index(drop=True)
bersih["harga_ribu"] = bersih["harga_ribu"].interpolate().round(1)
# Tabel fitur lag hasil Bagian 6, lalu pembagian kronologis 28/6.
fitur = pd.DataFrame(
{
"lag_1": bersih["harga_ribu"].shift(1),
"lag_2": bersih["harga_ribu"].shift(2),
"minggu_ke": bersih["harga_ribu"].index + 1,
"target": bersih["harga_ribu"],
"minggu": bersih["minggu"],
}
).dropna().reset_index(drop=True)
fitur["minggu_ke"] = fitur["minggu_ke"].astype(int)
latih = fitur.iloc[:-6].copy()
uji = fitur.iloc[-6:].copy()
# Model Bagian 8: LinearRegression pada fitur lag (deterministik).
FITUR_KOLOM = ["lag_1", "lag_2", "minggu_ke"]
model = LinearRegression()
model.fit(latih[FITUR_KOLOM], latih["target"])
prakiraan_model = model.predict(uji[FITUR_KOLOM])
prakiraan_naive = uji["lag_1"].to_numpy()
aktual = uji["target"].to_numpy()
tabel = pd.DataFrame(
{
"minggu": uji["minggu"],
"aktual": aktual,
"prakiraan_model": np.round(prakiraan_model, 2),
"prakiraan_naive": prakiraan_naive,
"galat_model": np.round(np.abs(aktual - prakiraan_model), 2),
"galat_naive": np.abs(aktual - prakiraan_naive),
}
)
print("Pertandingan pada 6 minggu uji W31-W36 (ribu rupiah per kg):")
print(tabel.to_string(index=False))Output yang Dihasilkan:
Pertandingan pada 6 minggu uji W31-W36 (ribu rupiah per kg):
minggu aktual prakiraan_model prakiraan_naive galat_model galat_naive
W31 39.1 40.25 38.1 1.15 1.0
W32 40.2 41.54 39.1 1.34 1.1
W33 42.5 42.23 40.2 0.27 2.3
W34 43.0 44.24 42.5 1.24 0.5
W35 41.9 43.40 43.0 1.50 1.1
W36 40.9 41.88 41.9 0.98 1.0Tabel ini sudah bercerita sebelum diringkas. Pada W31 dan W32 kedua metode meleset ke arah yang berlawanan — model terlalu tinggi (40,25 dan 41,54 di atas harga aktual), naive terlalu rendah karena tertinggal satu langkah di bawah tanjakan. Pada W33, minggu dengan kenaikan paling tajam, model nyaris tepat (galat 0,27) sementara naive tertinggal 2,3. Pada W34 naive justru lebih dekat, dan pada W35 keduanya sama-sama terlalu tinggi di lereng. Baris W36 nyaris seri: 0,98 lawan 1,0.
Menghitung MAE dan RMSE Kedua Metode #
Enam pasang galat perlu diringkas menjadi angka tunggal agar dapat dibandingkan. Kita pakai dua ukuran yang sama dengan Bagian 7 — MAE, rata-rata galat mutlak yang terbaca langsung sebagai rupiah, dan RMSE yang menghukum galat besar lebih berat karena galat dikuadratkan sebelum dirata-rata:
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
# Resep pembersihan hasil Bagian 4: duplikat dibuang, '-' diinterpolasi.
df = pd.read_csv("../data/harga_mingguan.csv")
bersih = df.drop_duplicates(subset=["minggu"], keep="first").copy()
bersih["harga_ribu"] = pd.to_numeric(bersih["harga_ribu"], errors="coerce")
bersih = bersih.sort_values("tanggal_mulai").reset_index(drop=True)
bersih["harga_ribu"] = bersih["harga_ribu"].interpolate().round(1)
# Tabel fitur lag hasil Bagian 6, lalu pembagian kronologis 28/6.
fitur = pd.DataFrame(
{
"lag_1": bersih["harga_ribu"].shift(1),
"lag_2": bersih["harga_ribu"].shift(2),
"minggu_ke": bersih["harga_ribu"].index + 1,
"target": bersih["harga_ribu"],
"minggu": bersih["minggu"],
}
).dropna().reset_index(drop=True)
fitur["minggu_ke"] = fitur["minggu_ke"].astype(int)
latih = fitur.iloc[:-6].copy()
uji = fitur.iloc[-6:].copy()
# Model Bagian 8: LinearRegression pada fitur lag (deterministik).
FITUR_KOLOM = ["lag_1", "lag_2", "minggu_ke"]
model = LinearRegression()
model.fit(latih[FITUR_KOLOM], latih["target"])
galat_model = uji["target"].to_numpy() - model.predict(uji[FITUR_KOLOM])
galat_naive = uji["target"].to_numpy() - uji["lag_1"].to_numpy()
mae_model = float(np.mean(np.abs(galat_model)))
mae_naive = float(np.mean(np.abs(galat_naive)))
rmse_model = float(np.sqrt(np.mean(galat_model**2)))
rmse_naive = float(np.sqrt(np.mean(galat_naive**2)))
print(f"Jumlah galat mutlak model : {np.abs(galat_model).sum():.2f} ribu -> MAE {mae_model:.3f}")
print(f"Jumlah galat mutlak naive : {np.abs(galat_naive).sum():.2f} ribu -> MAE {mae_naive:.3f}")
print(f"Jumlah kuadrat galat model : {(galat_model**2).sum():.2f} -> RMSE {rmse_model:.3f}")
print(f"Jumlah kuadrat galat naive : {(galat_naive**2).sum():.2f} -> RMSE {rmse_naive:.3f}")
print()
print(f"Model lebih baik dari baseline pada MAE : {(mae_naive - mae_model) / mae_naive * 100:.1f}%")
print(f"Model lebih baik dari baseline pada RMSE : {(rmse_naive - rmse_model) / rmse_naive * 100:.1f}%")Output yang Dihasilkan:
Jumlah galat mutlak model : 6.48 ribu -> MAE 1.080
Jumlah galat mutlak naive : 7.00 ribu -> MAE 1.167
Jumlah kuadrat galat model : 7.95 -> RMSE 1.151
Jumlah kuadrat galat naive : 9.96 -> RMSE 1.288
Model lebih baik dari baseline pada MAE : 7.4%
Model lebih baik dari baseline pada RMSE : 10.7%Mari kita baca keempat baris pertama. Model mencatat MAE 1,080 ribu — rata-rata meleset 1.080 rupiah per kilogram per minggu — mengalahkan naive yang mencatat 1,167 ribu; untuk belanja 10 kilogram cabai setiap pekan, selisih itu sekitar Rp900 per minggu. Kemenangan pada RMSE bahkan lebih lebar: 10,7%. Perhatikan pula selisih RMSE terhadap MAE masing-masing metode: model 1,151 berbanding 1,080 (renggang 0,07), naive 1,288 berbanding 1,167 (renggang 0,12). RMSE yang menjulang di atas MAE selalu menandakan satu galat menonjol — pada naive, penyumbang terbesarnya adalah kuadrat galat W33 (5,29 dari jumlah kuadrat 9,96). Model tidak memiliki minggu seburuk itu: galat terbesarnya hanya 1,50. Ragam ukuran evaluasi prakiraan dan cara membacanya dibahas tuntas pada chapter Evaluasi Forecasting , sedangkan rincian resmi metrik regresi di scikit-learn ada pada dokumentasi model evaluation .
Kemenangan Tipis yang Dibaca Jujur #
Angka 7,4% terdengar sebagai kemenangan yang rapi, tetapi tabel per minggu di atas menyimpan kenyataan yang lebih halus: pada mayoritas minggu, model justru kalah dari naive. Blok berikut menghitungnya secara eksplisit:
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
# Resep pembersihan hasil Bagian 4: duplikat dibuang, '-' diinterpolasi.
df = pd.read_csv("../data/harga_mingguan.csv")
bersih = df.drop_duplicates(subset=["minggu"], keep="first").copy()
bersih["harga_ribu"] = pd.to_numeric(bersih["harga_ribu"], errors="coerce")
bersih = bersih.sort_values("tanggal_mulai").reset_index(drop=True)
bersih["harga_ribu"] = bersih["harga_ribu"].interpolate().round(1)
# Tabel fitur lag hasil Bagian 6, lalu pembagian kronologis 28/6.
fitur = pd.DataFrame(
{
"lag_1": bersih["harga_ribu"].shift(1),
"lag_2": bersih["harga_ribu"].shift(2),
"minggu_ke": bersih["harga_ribu"].index + 1,
"target": bersih["harga_ribu"],
"minggu": bersih["minggu"],
}
).dropna().reset_index(drop=True)
fitur["minggu_ke"] = fitur["minggu_ke"].astype(int)
latih = fitur.iloc[:-6].copy()
uji = fitur.iloc[-6:].copy()
# Model Bagian 8: LinearRegression pada fitur lag (deterministik).
FITUR_KOLOM = ["lag_1", "lag_2", "minggu_ke"]
model = LinearRegression()
model.fit(latih[FITUR_KOLOM], latih["target"])
galat_model = np.abs(uji["target"].to_numpy() - model.predict(uji[FITUR_KOLOM]))
galat_naive = np.abs(uji["target"].to_numpy() - uji["lag_1"].to_numpy())
menang = galat_model < galat_naive
menang_model = ", ".join(m for m, s in zip(uji["minggu"], menang) if s)
menang_naive = ", ".join(m for m, s in zip(uji["minggu"], menang) if not s)
print(f"Minggu dimenangkan model : {int(menang.sum())} dari 6 ({menang_model})")
print(f"Minggu dimenangkan naive : {6 - int(menang.sum())} dari 6 ({menang_naive})")
print()
print(f"Galat terbesar model : {galat_model.max():.2f} ribu ({uji['minggu'].iloc[int(np.argmax(galat_model))]})")
print(f"Galat terbesar naive : {galat_naive.max():.2f} ribu ({uji['minggu'].iloc[int(np.argmax(galat_naive))]})")
print()
unggul = (galat_naive - galat_model)[menang].sum()
tertinggal = (galat_model - galat_naive)[~menang].sum()
print(f"Keunggulan model pada dua minggu kemenangannya : {unggul:.2f} ribu")
print(f"Ketertinggalan model pada empat minggu kekalahannya : {tertinggal:.2f} ribu")
print(f"Selisih bersih enam minggu : {unggul - tertinggal:.2f} ribu untuk model")Output yang Dihasilkan:
Minggu dimenangkan model : 2 dari 6 (W33, W36)
Minggu dimenangkan naive : 4 dari 6 (W31, W32, W34, W35)
Galat terbesar model : 1.50 ribu (W35)
Galat terbesar naive : 2.30 ribu (W33)
Keunggulan model pada dua minggu kemenangannya : 2.05 ribu
Ketertinggalan model pada empat minggu kekalahannya : 1.53 ribu
Selisih bersih enam minggu : 0.52 ribu untuk modelInilah temuan paling penting pada bagian ini, dan membacanya membutuhkan kejujuran. Model hanya memenangkan dua dari enam minggu — W33 dengan telak (selisih 2,03) dan W36 nyaris seri (selisih 0,02) — sementara naive menang pada empat minggu dengan total ketertinggalan model 1,53 ribu. Seluruh keunggulan bersih 0,52 ribu itu lahir dari satu minggu saja: W33, minggu saat harga berbalik naik paling tajam, momen ketika mesin momentum model (bobot 0,899 dari Bagian 8) benar-benar berfungsi sementara naive tertinggal jauh. Kesimpulan yang jujur ada tiga. Pertama, model memang lebih baik secara rata-rata — 7,4% pada MAE dan 10,7% pada RMSE bukan angka karangan. Kedua, keunggulan itu rapuh: bila jendela uji kebetulan tidak memuat minggu berbalik cepat, model bisa saja kalah dari naive. Ketiga, keunggulan model yang paling kokoh justru bukan kemenangan rata-rata, melainkan keterkendalian: galat terbesar model 1,50 lawan 2,30 milik naive, sehingga prakiraan model lebih bisa diandalkan sebagai batas kerugian terburuk.
Ringkasan Bagian 9 #
Pada enam minggu uji W31–W36, model LinearRegression mencatat MAE 1,080 ribu dan RMSE 1,151 ribu, mengalahkan baseline naive yang mencatat 1,167 dan 1,288 — unggul 7,4% pada MAE dan 10,7% pada RMSE. Tetapi bedah per minggu menunjukkan kemenangan itu lahir dari satu minggu berbalik cepat (W33): model hanya menang dua dari enam minggu, dan seluruh selisih bersih 0,52 ribu berasal dari keunggulan 2,03 ribu pada W33. Model layak dipakai dengan dua catatan — keunggulannya tipis, dan nilai terbesarnya adalah galat terbesar yang lebih kecil (1,50 lawan 2,30).
Rata-rata telah dihitung; kini saatnya bertanya mengapa model meleset pada minggu-minggu tertentu. Jawabannya ada pada tanda dan waktu galat, yang kita bedah pada Bagian 10: Menganalisis Kesalahan .