Prakiraan berupa satu angka telanjang — “harga minggu depan 41,5 ribu” — berpura-pura tahu masa depan lebih pastian daripada kenyataannya. Bagian 10 telah mengukur seberapa jauh model bisa meleset; pada bagian ini ukuran itu kita ubah menjadi sesuatu yang berguna: interval prakiraan, kisaran tempat harga mungkin berada. Kita bangun interval 95% dari simpangan residu, prakirakan empat minggu ke depan secara berantai, menyaksikan intervalnya melebar makin jauh horizonnya, lalu melatih cara mengatakannya kepada Bu Warni tanpa janji yang tidak bisa dipenuhi.

Dari Satu Angka ke Satu Kisaran #

Ide dasarnya sederhana. Prakiraan titik model adalah pusat perkiraan; galat-galat masa lalu memberi tahu seberapa jauh kenyataan biasanya berdeviasi dari pusat itu. Enam residu uji dari Bagian 10 (−1,50 sampai +0,27) adalah arsip pengalaman meleset milik model, dan simpangan bakunya menjadi satuan ukur ketidakpastian. Untuk interval 95% kita memakai konvensi ±1,96 kali simpangan baku — angka 1,96 berasal dari distribusi normal, tempat kira-kira 95% nilai berada dalam jarak itu dari pusat; asal-usul konvensi ini dibahas tuntas pada chapter Interval Kepercayaan . Satu penyesuaian khusus prakiraan: horizon yang makin jauh menuntut interval yang makin lebar, dan faktor pertumbuhannya kita ambil dari akar horizon — alasan mekanismenya menyusul setelah angkanya.

Simpangan Residu sebagai Ukuran Ketidakpastian #

Blok berikut menghitung simpangan baku enam residu uji dan menerjemahkannya menjadi lebar interval untuk horizon satu sampai empat minggu. Perhatikan ddof=1: pembagi jumlah residu dikurangi satu, bentuk jujur simpangan baku sampel ketika datanya cuma enam:

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()
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"])

residu_uji = uji["target"].to_numpy() - model.predict(uji[FITUR_KOLOM])
sigma = float(np.std(residu_uji, ddof=1))

print(f"Simpangan baku residu uji (ddof=1) : {sigma:.3f} ribu")
print(f"1.96 x sigma                       : {1.96 * sigma:.3f} ribu")
print()
print("Lebar interval 95% per horizon:")
for h in range(1, 5):
    lebar = 1.96 * sigma * np.sqrt(h)
    print(f"  horizon {h} minggu : +/- {lebar:.2f} ribu (lebar total {2 * lebar:.2f})")

Output yang Dihasilkan:

text
Simpangan baku residu uji (ddof=1) : 0.641 ribu
1.96 x sigma                       : 1.256 ribu

Lebar interval 95% per horizon:
  horizon 1 minggu : +/- 1.26 ribu (lebar total 2.51)
  horizon 2 minggu : +/- 1.78 ribu (lebar total 3.55)
  horizon 3 minggu : +/- 2.18 ribu (lebar total 4.35)
  horizon 4 minggu : +/- 2.51 ribu (lebar total 5.02)

Angka kuncinya: simpangan residu 0,641 ribu, sehingga interval satu minggu berbatas ±1,26 ribu. Mengapa batas ini perlu dikali akar horizon? Karena prakiraan berantai memakan prakiraannya sendiri. Prakiraan W38 memakai lag_1 hasil prakiraan W37; kesalahan kecil pada W37 mengalir masuk sebagai masukan W38, lalu ke W39, dan seterusnya. Bila galat antar langkah dianggap saling bebas, simpangan baku total tumbuh sebanding akar jumlah langkah — akar 2 untuk dua minggu, akar 4 untuk empat minggu — sehingga batas ±1,26 menjadi ±2,51. Ini aturan praktis yang sederhana dan jujur: ketidakpastian menumpuk seiring jarak, dan kita menuliskannya alih-alih menyembunyikannya.

Prakiraan Empat Minggu dan Interval yang Melebar #

Sekarang kita jalankan mesin prakiraannya. Riwayat data berakhir pada W36 (harga 40,9); empat minggu berikutnya diprakirakan berantai — setiap prakiraan baru ditambahkan ke riwayat untuk menjadi lag bagi prakiraan sesudahnya:

python
from datetime import timedelta

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"])

residu_uji = uji["target"].to_numpy() - model.predict(uji[FITUR_KOLOM])
sigma = float(np.std(residu_uji, ddof=1))

# Prakiraan rekursif: prakiraan minggu jauh memakai prakiraan minggu dekat.
riwayat = bersih["harga_ribu"].tolist()
tanggal = pd.to_datetime(bersih["tanggal_mulai"]).iloc[-1]
print("Prakiraan empat minggu setelah W36, interval 95%:")
for h in range(1, 5):
    fitur_baru = pd.DataFrame(
        {"lag_1": [riwayat[-1]], "lag_2": [riwayat[-2]], "minggu_ke": [len(riwayat) + 1]}
    )
    titik = float(model.predict(fitur_baru)[0])
    lebar = 1.96 * sigma * np.sqrt(h)
    tanggal = tanggal + timedelta(days=7)
    print(f"  W{36 + h} ({tanggal.strftime('%Y-%m-%d')}) : {titik:.1f} ribu  [{titik - lebar:.1f}, {titik + lebar:.1f}]")
    riwayat.append(titik)

Output yang Dihasilkan:

text
Prakiraan empat minggu setelah W36, interval 95%:
  W37 (2026-02-09) : 41.5 ribu  [40.3, 42.8]
  W38 (2026-02-16) : 42.9 ribu  [41.2, 44.7]
  W39 (2026-02-23) : 44.3 ribu  [42.1, 46.4]
  W40 (2026-03-02) : 44.9 ribu  [42.4, 47.4]

Baca hasilnya baris demi baris. Titik prakiraan menanjak dari 41,5 ke 44,9 — model melihat harga terakhir sedikit turun (W35 41,9 ke W36 40,9), tetapi intersep dan fitur tren menarik prakiraan ke atas, lalu prakiraan-prakiraan tinggi itu saling menopang pada langkah berikutnya. Yang lebih penting daripada titiknya adalah braketnya: W37 berada di kisaran 40,3–42,8 yang masih sempit, tetapi W40 sudah melebar hingga 42,4–47,4 — rentang hampir 5 ribu rupiah. Perlu diakui pula bahwa titik W39 dan W40 (44,3 dan 44,9) melampaui seluruh harga tercatat pada jendela uji; model di sini sekadar meneruskan tren, bukan membaca siklus panen, sehingga angka-angka jauh itu layak dibaca sebagai skenario, bukan ramalan. Grafik berikut memperlihatkan keseluruhan bentuknya:

python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from datetime import timedelta
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"])

residu_uji = uji["target"].to_numpy() - model.predict(uji[FITUR_KOLOM])
sigma = float(np.std(residu_uji, ddof=1))

# Prakiraan rekursif 4 minggu beserta batas intervalnya.
riwayat = bersih["harga_ribu"].tolist()
titik_jauh, bawah, atas = [], [], []
for h in range(1, 5):
    fitur_baru = pd.DataFrame(
        {"lag_1": [riwayat[-1]], "lag_2": [riwayat[-2]], "minggu_ke": [len(riwayat) + 1]}
    )
    t = float(model.predict(fitur_baru)[0])
    lebar = 1.96 * sigma * np.sqrt(h)
    titik_jauh.append(t)
    bawah.append(t - lebar)
    atas.append(t + lebar)
    riwayat.append(t)

riil = bersih["harga_ribu"].to_numpy()
fig, ax = plt.subplots(figsize=(9, 4.2))
ax.plot(range(25, 37), riil[24:], marker="o", markersize=3.5, linewidth=1.6,
        color="#1f77b4", label="harga tercatat W25-W36")
ax.plot(range(36, 41), [riil[-1]] + titik_jauh, marker="s", markersize=4,
        linestyle="--", color="tab:orange", label="prakiraan W37-W40")
ax.fill_between(range(36, 41), [riil[-1]] + bawah, [riil[-1]] + atas,
                color="tab:orange", alpha=0.18, label="interval 95% (melebar)")
ax.set_xlabel("minggu ke-")
ax.set_ylabel("harga (ribu rupiah per kg)")
ax.set_title("Prakiraan empat minggu dengan interval yang melebar")
ax.set_xticks(range(25, 41, 2))
ax.legend(loc="upper left")
ax.grid(alpha=0.3)
fig.tight_layout()
plt.show()

Bentuk grafiknya seperti kipas yang membuka ke kanan. Garis biru menorehkan harga tercatat W25–W36 — lembah di W29–W31, tanjakan ke puncak W34, lalu dua minggu melandai. Dari titik terakhir W36, garis putus-putus jingga menanjak sebagai prakiraan, diapit pita semu yang makin lebar: sempit di W37, lalu melebar penuh menjelang W40. Pita itulah pesan visual bagian ini — semakin jauh kita memandang, semakin samar masa depannya. Ragam grafik garis dan area seperti ini dipetakan pada ringkasan API pyplot matplotlib .

Kalimat yang Jujur untuk Bu Warni #

Angka sudah lengkap; bagian tersulit justru mengucapkannya. Interval hanya berguna bila dikomunikasikan dengan bahasa yang tidak menjual kepastian palsu. Tabel berikut memusatkan aturan mainnya:

Boleh dikatakanSebaiknya dihindari
“Harga cabai W37 diperkirakan 41,5 ribu, kemungkinan besar di kisaran 40,3–42,8 ribu.”“Harga cabai minggu depan pasti 41,5 ribu.”
“Dua sampai empat minggu ke depan, anggap kisaran 41,2–47,4; angka tepatnya belum bisa dipastikan.”“Harga akan terus naik sampai 44,9 ribu pada W40.”
“Bila harga bergerak mendekati batas atas, itu skenario waspada; batas bawah adalah skenario hemat.”“Model kami akurat sehingga belanja bisa dihitung sampai rupiah terakhir.”

Ketiga kalimat kiri memakai kata “diperkirakan”, “kisaran”, dan “skenario” — kosakata yang dijanjikan sejak Bagian 1. Ketiganya juga menerjemahkan interval menjadi keputusan: horizon satu minggu (beli berapa kilo) cukup memakai kisaran W37 yang sempit; horizon dua sampai empat minggu (menunda atau mengejar pembelian, menggeser menu) wajib membaca kisaran melebar, dengan batas bawah sebagai skenario hemat dan batas atas sebagai skenario waspada.

Ringkasan Bagian 11 #

Interval 95% dibangun dari simpangan residu uji 0,641 ribu: prakiraan satu minggu berbatas ±1,26 ribu, dan batas itu tumbuh sebanding akar horizon hingga ±2,51 ribu pada empat minggu. Prakiraan berantai W37–W40 menghasilkan 41,5; 42,9; 44,3; 44,9 ribu dengan interval yang melebar dari 40,3–42,8 hingga 42,4–47,4 — bentuk kipas yang jujur tentang masa depan yang makin samar. Bahasa pelaporannya pun sudah dilatih: diperkirakan, kisaran, skenario hemat, skenario waspada.

Semua bahan kini tersedia — masalah yang dirumuskan, model yang menang tipis, kesalahan yang dipetakan, interval yang melebar. Saatnya merangkainya menjadi laporan utuh, pada Bagian 12: Menyusun Laporan .