Model machine learning yang canggih tidak berhak dipakai sebelum membuktikan dirinya melawan lawan yang paling sederhana. Pada bagian ini kita membangun lawan itu: prakiraan naive yang mengatakan “harga minggu depan sama dengan harga minggu ini”. Baseline semacam ini hampir tanpa usaha, tetapi justru karena itulah ia jujur — dan di deret harga yang menempel pada dirinya sendiri (korelasi lag-1 0,728), prakiraan malas seperti ini bisa saja sulit dikalahkan.
Apa Itu Baseline dan Mengapa Wajib #
Baseline adalah prakiraan acuan yang dibangun tanpa model — semurah mungkin, tanpa fitur, tanpa pelatihan. Ia berfungsi seperti garis start lomba: model seberapa pun rumitnya harus menghasilkan kesalahan lebih kecil daripada baseline pada data yang sama; bila tidak, kerumitannya hanya membebani. Baseline juga menafsirkan skala: galat 1 ribu rupiah hanya bisa dinilai besar atau kecil setelah tahu seberapa jauh prakiraan sederhana meleset.
Pilihan kita adalah naive last-value: prakiraan minggu ke-t adalah harga minggu ke-t−1. Pada tabel fitur Bagian 6, prakiraan ini sudah tersedia gratis sebagai kolom lag_1 — cukup menjajarkan target dengan lag_1 pada enam minggu uji:
import pandas as pd
# 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)
uji = fitur.iloc[-6:].copy()
# Baseline naive: harga minggu ini dipakai sebagai prakiraan minggu depan.
tabel = pd.DataFrame(
{
"minggu": uji["minggu"],
"aktual": uji["target"],
"prakiraan_naive": uji["lag_1"],
}
)
galat = tabel["aktual"] - tabel["prakiraan_naive"]
tabel["galat"] = galat.map("{:+.1f}".format)
print("Prakiraan naive pada 6 minggu uji (W31-W36):")
print(tabel.to_string(index=False))Output yang Dihasilkan:
Prakiraan naive pada 6 minggu uji (W31-W36):
minggu aktual prakiraan_naive galat
W31 39.1 38.1 +1.0
W32 40.2 39.1 +1.1
W33 42.5 40.2 +2.3
W34 43.0 42.5 +0.5
W35 41.9 43.0 -1.1
W36 40.9 41.9 -1.0Enam baris itu membaca kisah jendela uji dengan sangat jujur. Pada W31–W34 harga menanjak, dan prakiraan naive selalu tertinggal satu langkah di bawah — galat positif berturut-turut. Pada W35–W36 harga berbalik turun, dan kini prakiraan tertinggal di atas — galat berbalik negatif. Pola “selalu terlambat satu minggu” inilah kelemahan bawaan naive last-value: ia mengambil posisi hari ini sebagai tebakan besok, sehingga setiap perubahan arah pasti membuatnya meleset sebesar perubahan itu sendiri. Perhatikan pula bahwa galat terbesar, 2,3 ribu pada W33, jatuh tepat pada minggu kenaikan paling tajam — detail yang akan kembali muncul pada Bagian 9 dan 10.
Mengukur Kesalahan dengan MAE dan RMSE #
Enam galat perlu diringkas menjadi satu angka agar bisa dibandingkan antar metode. Kita pakai dua ukuran yang paling umum untuk regresi:
galat e_i = aktual_i - prakiraan_i
MAE = rata-rata |e_i|
RMSE = akar( rata-rata e_i^2 )MAE dibaca langsung sebagai rupiah: rata-rata jarak prakiraan dari kenyataan. RMSE menghukum galat besar lebih berat daripada galat kecil karena galat dikuadratkan sebelum dirata-rata — kedua ukuran jarang berbeda jauh, dan selisihnya justru memberi tahu ada tidaknya satu dua galat yang menonjol. Kedua rumus itu diterapkan pada keenam galat baseline:
import numpy as np
import pandas as pd
# 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)
uji = fitur.iloc[-6:].copy()
galat = (uji["target"] - uji["lag_1"]).to_numpy()
mae = float(np.mean(np.abs(galat)))
rmse = float(np.sqrt(np.mean(galat**2)))
terburuk = uji["minggu"].iloc[int(np.argmax(np.abs(galat)))]
print(f"Jumlah galat mutlak : {np.abs(galat).sum():.1f} ribu (6 minggu)")
print(f"MAE = 7.0 / 6 : {mae:.3f} ribu per minggu")
print(f"Jumlah kuadrat galat : {(galat**2).sum():.2f}")
print(f"RMSE = akar(9.96/6) : {rmse:.3f} ribu")
print(f"Galat terbesar : {terburuk} ({np.abs(galat).max():.1f} ribu), minggu dengan kenaikan paling tajam")Output yang Dihasilkan:
Jumlah galat mutlak : 7.0 ribu (6 minggu)
MAE = 7.0 / 6 : 1.167 ribu per minggu
Jumlah kuadrat galat : 9.96
RMSE = akar(9.96/6) : 1.288 ribu
Galat terbesar : W33 (2.3 ribu), minggu dengan kenaikan paling tajamMari kita baca hasilnya:
- MAE 1,167 ribu: rata-rata, prakiraan naive meleset 1.167 rupiah per kilogram per minggu. Untuk belanja 10 kilogram cabai setiap pekan — kisaran wajar warung — selisih itu berarti sekitar Rp11.700 per minggu, atau hampir Rp50.000 dalam sebulan.
- RMSE 1,288 ribu, lebih besar dari MAE: selisih 0,12 ribu ini adalah tanda satu galat menonjol — kuadrat galat W33 (5,29) menyumbang lebih dari separuh jumlah kuadrat seluruhnya (9,96). RMSE yang menjulang di atas MAE selalu bermakna sama: ada satu titik yang jauh lebih besar dari lainnya.
- Angka-angka ini berlaku hanya untuk jendela uji: enam minggu relatif tenang ini bukan ukuran mutlak deret. Bagian 8 akan menunjukkan bahwa pada 28 minggu latih — yang memuat lonjakan W19–W21 — naive jauh lebih buruk.
Ringkasan Bagian 7 #
Baseline naive last-value mencatat MAE 1,167 ribu dan RMSE 1,288 ribu pada enam minggu uji, dengan galat terbesar 2,3 ribu pada W33 saat harga menanjak paling tajam. Angka 1,167 inilah garis start: model pada Bagian 8 harus mengalahkannya untuk berhak disebut berguna. Berbagai ukuran evaluasi prakiraan lain — termasuk MAPE dan pembanding musiman — dibahas pada chapter Evaluasi Forecasting .
Model penantangnya kini siap dilatih, pada Bagian 8: Melatih Model .