Keluhan terbesar kita terhadap moving average — semua hari dalam jendela diberi bobot sama padahal informasi terbaru lebih berharga — diselesaikan oleh exponential smoothing: beri bobot terbesar pada pengamatan terbaru, lalu bobot menurun secara eksponensial untuk pengamatan yang makin lama. Dari gagasan sederhana itu lahir keluarga model peramal pertama kita yang sungguhan: Simple Exponential Smoothing (SES) untuk deret datar, dan Holt-Winters yang menambahkan tren serta musiman. Pada chapter ini kita menuliskan rumusnya, mempraktikkan keduanya dengan statsmodels pada data Toko Kopi Kita, dan merasakan bedanya ketika model akhirnya tahu bahwa Sabtu memang selalu ramai.

Bobot yang Menurun Eksponensial #

SES memelihara satu angka bernama level — taksiran rata-rata terkini deret — yang diperbarui setiap ada pengamatan baru:

text
s_t = alpha * y_t + (1 - alpha) * s_(t-1)

s_t     = level (taksiran level) setelah melihat pengamatan ke-t
alpha   = parameter pemulusan, 0 < alpha < 1
y_t     = pengamatan terbaru
s_(t-1) = level periode sebelumnya

Jika rumus itu kita urai berulang kali, level terbukti merupakan rata-rata tertimbang seluruh masa lalu dengan bobot alpha, alpha(1-alpha), alpha(1-alpha)^2, … yang menurun secara geometris — dari situlah nama exponential. Ramalan untuk semua periode mendatang cukup satu angka: level terakhir, s_T. Parameter alpha mengatur karakter model:

alphaRespons terhadap perubahanGaris ramalanCocok untuk
0.1 - 0.3Lambat, stabilSangat halusDeret tenang, perubahan langka
0.5SedangHalusKeseimbangan umum
0.7 - 0.9Cepat, responsifMengikuti dataDeret yang sering bergeser

Simple Exponential Smoothing dengan statsmodels #

Kita latih SES pada 35 hari pertama, meramalkan 7 hari terakhir, dan membandingkan dua karakter: alpha=0.2 yang halus dan alpha=0.8 yang responsif. Argumen optimized=False membuat statsmodels memakai nilai alpha kita apa adanya, bukan menaksirnya dari data — sehingga karakter kedua model benar-benar kita yang pegang.

python
import numpy as np
import pandas as pd
from statsmodels.tsa.holtwinters import SimpleExpSmoothing

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:]

ses_halus = SimpleExpSmoothing(latih, initialization_method="estimated").fit(
    smoothing_level=0.2, optimized=False
)
ses_cepat = SimpleExpSmoothing(latih, initialization_method="estimated").fit(
    smoothing_level=0.8, optimized=False
)

ramal_halus = ses_halus.forecast(7)
ramal_cepat = ses_cepat.forecast(7)

banding = pd.DataFrame(
    {
        "aktual": ujian.values,
        "alpha_0.2": ramal_halus.round(1).values,
        "alpha_0.8": ramal_cepat.round(1).values,
    },
    index=ujian.index,
)
print(banding)
print()
mae_halus = np.abs(ramal_halus.values - ujian.values).mean()
mae_cepat = np.abs(ramal_cepat.values - ujian.values).mean()
print(f"MAE alpha 0.2: {mae_halus:.2f} gelas")
print(f"MAE alpha 0.8: {mae_cepat:.2f} gelas")

Output yang Dihasilkan:

text
            aktual  alpha_0.2  alpha_0.8
2025-02-10      66       66.2       73.1
2025-02-11      62       66.2       73.1
2025-02-12      61       66.2       73.1
2025-02-13      63       66.2       73.1
2025-02-14      74       66.2       73.1
2025-02-15      81       66.2       73.1
2025-02-16      78       66.2       73.1

MAE alpha 0.2: 6.75 gelas
MAE alpha 0.8: 7.73 gelas

Mari kita telaah hasilnya:

  1. SimpleExpSmoothing(latih).fit(...) memelihara level dari awal deret hingga akhir data latih; .forecast(7) mengembalikan level terakhir untuk tujuh hari ke depan — itulah mengapa kedua kolom ramalan berupa angka yang sama berulang: SES hanya punya satu informasi (level), tanpa tren dan tanpa musiman.
  2. alpha_0.2 berhenti di 66,2 karena bobot kecil membuat level nyaris tak terguncang pekan akhir yang tinggi; alpha_0.8 menanjak ke 73,1 karena levelnya cepat menyerap akhir pekan ramai (78-81 gelas) yang menutup data latih.
  3. Keduanya kalah telak dari kenyataan mingguan: aktual berayun 61 → 81 mengikuti irama Senin-Sabtu, sementara ramalan SES datar sempurna. MAE 6,75 vs 7,73 memang memenangkan alpha kecil, tetapi kemenangan itu datang dari keberuntungan level, bukan dari menangkap pola.

Holt-Winters: Menambahkan Tren dan Musiman #

SES yang datar jelas tidak cukup untuk deret kita. Keluarga Holt-Winters (di statsmodels: ExponentialSmoothing) menambahkan dua persamaan pembaruan lagi: trend (kemiringan yang juga diperbarui dengan pemulusan) dan seasonal (indeks per posisi dalam periode, dalam kasus kita tujuh hari). Ketiganya digabung saat meramal: level terkini + proyeksi tren + penyesuaian musiman hari yang diramal.

python
import numpy as np
import pandas as pd
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:]

hw = ExponentialSmoothing(
    latih,
    trend="add",
    seasonal="add",
    seasonal_periods=7,
    initialization_method="estimated",
).fit()

ramalan = hw.forecast(7)
banding = pd.DataFrame(
    {"aktual": ujian.values, "holt_winters": ramalan.round(1).values},
    index=ujian.index,
)
print(banding)
mae = np.abs(ramalan.values - ujian.values).mean()
print("MAE Holt-Winters:", round(mae, 2), "gelas")

Output yang Dihasilkan:

text
            aktual  holt_winters
2025-02-10      66          62.2
2025-02-11      62          63.6
2025-02-12      61          67.2
2025-02-13      63          67.2
2025-02-14      74          71.8
2025-02-15      81          78.0
2025-02-16      78          78.4
MAE Holt-Winters: 3.06 gelas

Mari kita baca hasilnya:

  1. trend="add" dan seasonal="add" menyatakan tren dan musiman ditambahkan ke level — padanan model aditif pada dekomposisi; seasonal_periods=7 memberi tahu model panjang satu periode musiman mingguan.
  2. .fit() tanpa argumen membiarkan statsmodels menaksir ketiga parameter pemulusan (level, tren, musiman) dari data — berbeda dengan blok sebelumnya yang memaksa alpha tertentu.
  3. Ramalan kini bernafas mingguan: Senin 62,2, menanjak ke Jumat 71,8, memuncak Sabtu 78,0, Minggu 78,4 — bentuk yang sama dengan pola aktual. MAE anjlok dari 6,75 (SES terbaik) ke 3,06 gelas, lebih dari setengahnya, semata-mata karena model akhirnya mengenal kalender.

Rangkuman #

  • SES memelihara satu level dengan bobot eksponensial menurun; rumus s_t = alpha*y_t + (1-alpha)*s_(t-1); ramalannya datar untuk semua horizon.
  • Alpha kecil menghasilkan ramalan halus dan stabil; alpha besar responsif tetapi mudah terperangkap fluktuasi ujung data — responsif tidak sama dengan akurat.
  • Holt-Winters menambahkan persamaan tren dan musiman; dengan seasonal_periods=7, ramalannya mengikuti irama mingguan dan MAE turun dari 6,75 ke 3,06 gelas.
  • Semua varian exponential smoothing tetap berbasis pemulusan: mereka mengekstrapolasi pola yang sedang berjalan, tanpa menanyakan mekanisme sebab-akibat.

Exponential smoothing meramal dengan memuluskan lalu meneruskan. Ada cara berpikir yang berbeda untuk pekerjaan yang sama: tuliskan nilai sekarang sebagai persamaan regresi atas nilai-nilai lampau — itulah model autoregressive, tokoh utama chapter berikutnya dan bata pertama ARIMA.

Referensi #

  1. statsmodels — Time Series Analysis (tsa)
  2. statsmodels — Documentation