Moving average adalah teknik pemulusan tertua sekaligus paling sering dipakai dalam analisis deret waktu: ganti tiap nilai dengan rata-rata nilai di sekitarnya agar noise mereda dan pola besar terlihat. Kita sebenarnya sudah memakainya — kolom tren pada dekomposisi dan rolling(7) pada chapter Tren dan Musiman keduanya moving average. Pada chapter ini kita angkat teknik ini ke meatan sendiri: bagaimana pemilihan lebar jendela mengubah hasil, mengapa jendela ganjil lebih disukai, dan di mana letak kelemahan utamanya — keterlambatan (lag) — yang membuat moving average lebih cocok sebagai alat baca masa lalu daripada alat peramal.
Cara Kerja Moving Average #
Simple moving average (MA) menghitung rata-rata dari sejumlah w nilai terakhir, lalu jendelanya bergeser satu titik tiap langkah:
MA_t(w) = (y_t + y_{t-1} + ... + y_{t-w+1}) / w
y_t = nilai deret pada waktu t
w = lebar jendela (jumlah titik yang dirata-ratakan)Efeknya dua arah sekaligus. Pertama, noise yang naik-turun bebas saling meniadakan di dalam jendela sehingga garis hasil jadi mulus — rata-rata dari w nilai noise memiliki simpangan yang mengecil. Kedua, perubahan sungguhan (lonjakan akhir pekan, awal tren) ikut terbagi rata ke seluruh jendela sehingga garis hasil bergerak lambat merespons. Pemulusan dan keterlambatan adalah dua sisi mata uang yang sama: makin lebar jendelanya, makin mulus hasilnya dan makin lambat responsnya. Jendela ganjil (3, 5, 7) disukai karena punya titik tengah yang tepat — nilai hasil dapat diatribusikan ke satu hari tertentu, bukan ke “antara dua hari”.
Perbandingan Jendela 3 Hari dan 7 Hari #
Kita buktikan trade-off itu pada data 30 hari pertama penjualan Toko Kopi Kita, dengan dua jendela: ma3 yang ramping dan ma7 yang selebar satu pekan penuh.
import numpy as np
import pandas as pd
rng = np.random.default_rng(42)
n = 30
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")
ma3 = deret.rolling(3).mean().round(1)
ma7 = deret.rolling(7).mean().round(1)
tabel = pd.DataFrame({"gelas": deret, "ma3": ma3, "ma7": ma7})
print(tabel.iloc[6:13])
print("...")
print(tabel.iloc[-3:])
puncak = deret.idxmax()
print()
print("Puncak deret:", puncak.date(), "=", deret.max(), "gelas")
print("Nilai ma3 di hari itu:", ma3[puncak], "| ma7:", ma7[puncak])Output yang Dihasilkan:
gelas ma3 ma7
2025-01-12 55 50.0 46.1
2025-01-13 43 50.0 46.4
2025-01-14 46 48.0 47.7
2025-01-15 44 44.3 47.4
2025-01-16 53 47.7 48.0
2025-01-17 58 51.7 50.1
2025-01-18 61 57.3 51.4
...
gelas ma3 ma7
2025-02-02 69 67.7 60.4
2025-02-03 58 66.3 61.6
2025-02-04 60 62.3 61.7
Puncak deret: 2025-02-01 = 72 gelas
Nilai ma3 di hari itu: 63.3 | ma7: 59.4Mari kita telaah hasilnya:
deret.rolling(3).mean()menghitung rata-rata nilai sekarang dan dua hari sebelumnya;rolling(7)memperlebar jendela menjadi tujuh. Kedua kolom baru terisi mulai baris ketiga dan ketujuh karena jendela yang belum penuh menghasilkanNaN— itulah harga yang dibayar di awal deret.- Kolom
ma3masih ikut bergelombang (44,3 lalu 57,3 dalam lima hari): jendela sempit meredam noise sedikit, tetapi gelombang mingguan yang besar tetap lolos. Kolomma7justru menanjak nyaris mulus (46,1 → 61,7) karena satu jendela penuh selalu memuat Senin sampai Minggu secara seimbang — musiman mingguan saling meniadakan di dalam jendela. - Tiga baris terakhir memperlihatkan sifat lag: pada puncak deret 1 Februari (72 gelas),
ma3baru terbaca 63,3 danma7hanya 59,4 — tertinggal 9 dan 13 gelas di belakang kenyataan. Moving average selalu melihat ke belakang; puncak hari ini terbagi rata dengan hari-hari biasa di dalam jendela. - Pemilihan jendela adalah keputusan analitis:
ma3untuk membaca pergerakan mingguan dalam mingguan,ma7untuk membaca tren — tidak ada satu angka yang benar untuk semua tujuan.
Blok berikut menggambarkan ketiga garis tersebut:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
rng = np.random.default_rng(42)
n = 30
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")
ma3 = deret.rolling(3).mean()
ma7 = deret.rolling(7).mean()
fig, ax = plt.subplots(figsize=(9, 4))
ax.plot(deret.index, deret.values, marker="o", markersize=3, label="Gelas terjual")
ax.plot(ma3.index, ma3.values, label="Moving average 3 hari")
ax.plot(ma7.index, ma7.values, label="Moving average 7 hari")
ax.set_ylabel("Gelas")
ax.legend()
plt.show()Pada grafik yang dihasilkan, garis titik-titik deret asli bergelombang paling liar mengikuti irama akhir pekan; garis ma3 masih bergelombang seirama tetapi rendah amplitudonya; dan garis ma7 tampak sebagai kurva mulus yang menanjak pelan dari sekitar 46 ke 61 gelas. Terlihat pula bahwa setiap puncak akhir pekan, garis ma7 berada di bawahnya — jarak vertikal itulah lag yang kita ukur lewat angka sebelumnya.
Kelemahan: Lag, Bobot Seragam, dan Ujung yang Kosong #
Tiga kelemahan moving average perlu kita catat dengan jujur. Pertama, lag di ujung: karena jendela hanya melihat ke belakang, garis MA selalu terlambat mengikuti perubahan — pada data bertren naik, MA sistematis berada di bawah nilai terkini, seperti tampak pada 63,3 vs 72 di puncak. Kedua, bobot seragam: hari kemarin dan hari enam hari lalu diperlakukan sama penting, padahal dalam banyak deret, informasi terbaru jauh lebih relevan. Ketiga, ujung yang kosong: rolling menyisakan NaN di awal (dan centered MA juga di akhir) sehingga selalu ada bagian deret yang tidak punya nilai hasil.
Varian centered moving average (rolling(7, center=True)) mengambil jendela simetris tiga hari ke depan dan tiga hari ke belakang, menghapus lag dan dipakai oleh seasonal_decompose untuk menaksir tren — tetapi sisi hukumannya jelas: tiga hari ke depan harus sudah ada, sehingga centered MA mustahil dihitung untuk hari ini, apalagi untuk meramal besok.
Rangkuman #
- Simple moving average mengganti tiap nilai dengan rata-rata
wnilai terakhir; makin lebar jendela, makin mulus hasilnya dan makin lambat responsnya. - Jendela ganjil (3, 5, 7) memberi titik tengah sehingga hasil teratribusi ke satu hari;
ma7satu pekan penuh meratakan musiman mingguan dan menonjolkan tren. - Lag bersifat sistematis: pada puncak 72 gelas, ma3 terbaca 63,3 dan ma7 59,4 — moving average selalu tertinggal pada perubahan.
- Centered moving average menghilangkan lag tetapi butuh data masa depan, sehingga hanya cocok untuk analisis historis; peramalan membutuhkan pendekatan lain.
Bobot seragam menjadi keluhan utama kita terhadap moving average: kenapa hari enam hari lalu harus sama berharganya dengan kemarin? Chapter berikutnya menjawabnya dengan bobot yang menurun secara eksponensial — exponential smoothing — yang sekaligus menjadi model peramalan pertama kita yang sesungguhnya.