Baseline sudah berdiri dengan MAE 1,167 ribu; kini giliran penantangnya. Kita melatih LinearRegression dari scikit-learn pada 28 baris fitur latih — model yang paling sederhana, paling cepat, dan paling mudah dibedah. Pilihan ini bukan kemalasan: pada 34 baris data, model yang bisa dibaca koefisiennya justru paling aman, karena setiap perilakunya dapat dijelaskan — dan kejanggalannya dapat dideteksi — tanpa menebak isi kotak hitam.

Mengapa Regresi Linear #

Regresi linear memodelkan harga sebagai jumlah tertimbang dari fitur: setiap fitur dikalikan satu koefisien, ditambah satu konstanta. Untuk masalah kita bentuknya:

text
prakiraan = intersep + b1 x lag_1 + b2 x lag_2 + b3 x minggu_ke

Model ini memiliki tiga sifat yang kita butuhkan. Pertama, pelatihnya berupa rumus tertutup least squares — tanpa iterasi acak, tanpa random_state, hasilnya identik setiap dijalankan; sejalan dengan komitmen deterministik seluruh proyek. Kedua, koefisiennya terbaca sebagai pengaruh masing-masing fitur, sehingga kita bisa memeriksa apakah model belajar hal yang masuk akal. Ketiga, sebagai model paling sederhana ia adalah tolok ukur yang adil: bila linear saja tidak mengalahkan baseline, model yang lebih berat wajib menjelaskan dirinya lebih dulu. Materi dasarnya ada pada chapter Model Regresi Linear , dan rincian API-nya pada dokumentasi LinearRegression .

Melatih Model pada 28 Minggu #

Pelatihan hanya membutuhkan dua baris — fit() mempelajari koefisien dari data, dan itulah seluruh “kecerdasan” yang diminta:

python
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()

# Latih regresi linear pada 28 minggu pertama (W3-W30).
FITUR_KOLOM = ["lag_1", "lag_2", "minggu_ke"]
model = LinearRegression()
model.fit(latih[FITUR_KOLOM], latih["target"])

prediksi_latih = model.predict(latih[FITUR_KOLOM])
mae_model = float(np.mean(np.abs(latih["target"].to_numpy() - prediksi_latih)))
mae_naive = float(np.mean(np.abs(latih["target"].to_numpy() - latih["lag_1"].to_numpy())))

print(f"Koefisien lag_1     : {model.coef_[0]:+.3f}")
print(f"Koefisien lag_2     : {model.coef_[1]:+.3f}")
print(f"Koefisien minggu_ke : {model.coef_[2]:+.3f}")
print(f"Intersep            : {model.intercept_:.3f}")
print(f"MAE model pada data latih (28 minggu) : {mae_model:.3f} ribu")
print(f"MAE naive pada data latih (28 minggu) : {mae_naive:.3f} ribu")

Output yang Dihasilkan:

text
Koefisien lag_1     : +1.151
Koefisien lag_2     : -0.647
Koefisien minggu_ke : +0.072
Intersep            : 18.890
MAE model pada data latih (28 minggu) : 1.181 ribu
MAE naive pada data latih (28 minggu) : 1.957 ribu

Dua baris terakhir memberi kabar penyemangat pada data latih: model mencatat MAE 1,181 ribu, jauh di bawah naive yang mencatat 1,957 ribu — unggul 39,7%. Selisih selebar itu sebagian besar disumbang lonjakan W19–W21 yang berada di jendela latih: naive tertinggal 9,5 ribu pada W19, sedangkan model setidaknya mengejar sebagian. Perlu digarisbawahi bahwa 1,181 adalah nilai pada data yang dipakai belajar; angka yang berarti baru muncul pada Bagian 9 saat model menghadapi enam minggu yang belum pernah dilihatnya.

Membaca Koefisien: Level, Momentum, dan Tren #

Tiga koefisien di atas bisa dibaca langsung, tetapi bentuk aslinya kurang elok — bagaimana mungkin harga dua minggu lalu “berpengaruh negatif”? Blok berikut menulis ulang persamaan yang sama dalam bentuk yang lebih bermakna, dengan memakai dua besaran pengganti: rata-rata harga dua minggu terakhir (level) dan selisih minggu terakhir terhadap sebelumnya (momentum):

python
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()

FITUR_KOLOM = ["lag_1", "lag_2", "minggu_ke"]
model = LinearRegression()
model.fit(latih[FITUR_KOLOM], latih["target"])

# Bongkar koefisien menjadi tiga pengaruh: level, momentum, tren.
bobot_level = model.coef_[0] + model.coef_[1]
bobot_momentum = (model.coef_[0] - model.coef_[1]) / 2
print(f"prakiraan = {model.intercept_:.3f}")
print(f"         + {bobot_level:.3f} x rata-rata harga dua minggu terakhir   (level)")
print(f"         + {bobot_momentum:.3f} x selisih harga minggu terakhir      (momentum)")
print(f"         + {model.coef_[2]:.3f} x nomor minggu ke-                   (tren)")
print()

# Dua skenario dengan level sama (rata-rata 41.0) tetapi arah berlawanan.
naik = pd.DataFrame({"lag_1": [42.0], "lag_2": [40.0], "minggu_ke": [20]})
turun = pd.DataFrame({"lag_1": [40.0], "lag_2": [42.0], "minggu_ke": [20]})
print(f"Skenario naik  (dua minggu terakhir 40.0 lalu 42.0): prakiraan {float(model.predict(naik)[0]):.2f}")
print(f"Skenario turun (dua minggu terakhir 42.0 lalu 40.0): prakiraan {float(model.predict(turun)[0]):.2f}")
print(f"Selisih prakiraan: {float(model.predict(naik)[0] - model.predict(turun)[0]):.2f} ribu, murni dari arah pergerakan")

Output yang Dihasilkan:

text
prakiraan = 18.890
         + 0.504 x rata-rata harga dua minggu terakhir   (level)
         + 0.899 x selisih harga minggu terakhir      (momentum)
         + 0.072 x nomor minggu ke-                   (tren)

Skenario naik  (dua minggu terakhir 40.0 lalu 42.0): prakiraan 42.78
Skenario turun (dua minggu terakhir 42.0 lalu 40.0): prakiraan 39.19
Selisih prakiraan: 3.60 ribu, murni dari arah pergerakan

Bentuk ulang ini menjelaskan perilaku model dengan tiga kalimat. Pertama, level: rata-rata harga dua minggu terakhir diberi bobot 0,504 — separuh dari level terkini diteruskan ke prakiraan. Kedua, momentum: selisih minggu terakhir diberi bobot 0,899, hampir satu banding satu — bila harga baru naik 1 ribu, prakiraan didorong naik lagi hampir 1 ribu; koefisien lag_2 yang negatif tadi ternyata bagian dari mesin momentum ini, bukan penolak harga lama. Ketiga, tren: setiap berjalannya minggu menambah 0,072 ribu — angka yang nyaris sama dengan kemiringan +0,089 yang kita ukur pada Bagian 5, kini ditemukan kembali sendiri oleh model.

Dua skenario di ujung keluaran membuktikan betapa menentukannya momentum: kedua skenario berbagi rata-rata yang sama (41,0), tetapi yang menanjak diprakirakan 42,78 sementara yang melandai diprakirakan 39,19 — selisih 3,60 ribu murni dari arah pergerakan. Model ini pada dasarnya berkata: “harga cenderung melanjutkan gerakannya yang terakhir, sedikit ditarik oleh tren kalender.” Sifat itulah yang kelak menjelaskan kemenangannya di minggu berubah cepat dan kekalahannya di minggu tenang.

Ringkasan Bagian 8 #

LinearRegression dilatih pada 28 baris fitur dan menghasilkan persamaan yang terbaca sebagai level (bobot 0,504), momentum (0,899), dan tren (0,072) dengan intersep 18,890. Pada data latih model mencatat MAE 1,181 ribu — unggul 39,7% dari naive yang mencatat 1,957 ribu.

Namun latihan bukan pertandingan. Angka yang menentukan lahir pada Bagian 9: Mengevaluasi Prediksi , ketika model menghadapi enam minggu yang belum pernah dilihatnya.