Autokorelasi lag 7 pada chapter sebelumnya memberitahu kita bahwa penjualan Toko Ceria memuat pola mingguan — tetapi pola itu masih tercampur dengan tren dan lonjakan kampanye di dalam satu deret. Dekomposisi deret waktu (time series decomposition) membedah deret menjadi tiga komponen yang lebih mudah dibaca: tren, musiman, dan residu. Hasil pembedahan ini bermanfaat untuk diagnosis (“apakah penjualan naik karena tren atau musiman?”), untuk membangun fitur bagi model, dan untuk menandai hari-hari tak wajar melalui residu yang besar. Kita akan memakai fungsi seasonal_decompose dari statsmodels pada data yang sama dengan chapter sebelumnya, lalu menghadapi variasi kedua: deret yang amplitudo musimannya tumbuh seiring level.
Dua Model Dekomposisi: Aditif dan Multiplikatif #
Model aditif menuliskan deret sebagai penjumlahan tiga komponen: y_t = T_t + S_t + R_t, dengan T_t tren, S_t komponen musiman, dan R_t residu. Model ini cocok ketika besar lonjakan musiman relatif tetap dari waktu ke waktu — misalnya akhir pekan selalu menambah sekitar 40 unit penjualan, baik saat toko baru buka maupun enam bulan kemudian. Model multiplikatif menuliskan deret sebagai hasil kali: y_t = T_t x S_t x R_t. Model kedua cocok ketika lonjakan musiman tumbuh seiring level: toko yang penjualannya menggandakan diri biasanya juga menggandakan lonjakan akhir pekannya, karena basis pembelinya yang membesar.
| Aspek | Model aditif | Model multiplikatif |
|---|---|---|
| Bentuk relasi | y_t = T_t + S_t + R_t | y_t = T_t x S_t x R_t |
| Pola musiman | Amplitudo tetap (unit) | Amplitudo sebanding level (persentase) |
| Komponen musiman | Nilai plus/minus dalam unit | Faktor pengali (sekitar 1,0) |
| Contoh khas | Pola jam sibuk harian yang stabil | Ritel yang tumbuh pesat, inflasi harga |
| Jembatan antar-model | — | Transformasi log mengubahnya menjadi aditif |
Dekomposisi Aditif pada Penjualan Toko Ceria #
Fungsi seasonal_decompose menerima deret dengan frekuensi teratur dan satu parameter wajib kita isi: period, yaitu panjang satu siklus musiman — tujuh hari untuk pola mingguan. Parameter ini menggantikan nama freq yang sudah tidak digunakan lagi pada versi statsmodels modern, jadi biasakan menulis period=7, bukan freq=7. Contoh berikut mendekomposisi 168 hari penjualan Toko Ceria, lalu membaca ketiga komponen dari atribut hasil: .trend, .seasonal, dan .resid.
# Dekomposisi aditif: membedah deret penjualan Toko Ceria menjadi
# tren, komponen musiman mingguan, dan residu dengan statsmodels.
import numpy as np
import pandas as pd
from statsmodels.tsa.seasonal import seasonal_decompose
def buat_deret_penjualan(n_hari=168):
rng = np.random.default_rng(42)
idx = pd.date_range(start="2026-01-05", periods=n_hari, freq="D")
t = np.arange(n_hari)
tren = 120.0 + 0.45 * t
hari = np.asarray(idx.dayofweek)
efek_hari = np.select(
[hari == 4, hari == 5, hari == 6],
[10.0, 32.0, 40.0],
default=0.0,
)
promo = ((t >= 70) & (t < 84)).astype(float) * 45.0
kilat = (t == 105).astype(float) * 85.0
derau = rng.normal(0.0, 12.0, n_hari)
y = tren + efek_hari + promo + kilat + derau
return pd.Series(np.round(y, 1), index=idx, name="penjualan")
penjualan = buat_deret_penjualan()
hasil = seasonal_decompose(penjualan, model="additive", period=7)
tren_bersih = hasil.trend.dropna()
print(f"Nilai tren NaN di awal dan akhir deret: "
f"{int(hasil.trend.iloc[:3].isna().sum())} dan {int(hasil.trend.iloc[-3:].isna().sum())} baris")
print(f"Tren awal ({tren_bersih.index[0].date()}): {tren_bersih.iloc[0]:8.2f}")
print(f"Tren akhir ({tren_bersih.index[-1].date()}): {tren_bersih.iloc[-1]:8.2f}")
sebelum = tren_bersih.loc["2026-03-08"]
tengah = tren_bersih.loc["2026-03-22"]
sesudah = tren_bersih.loc["2026-04-05"]
lonjakan = tengah - 0.5 * (sebelum + sesudah)
print(f"Lonjakan tren di tengah kampanye (di atas garis lurus): {lonjakan:+.2f}")
nama_hari = ["Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"]
print("\nKomponen musiman mingguan (unit):")
for nama, nilai in zip(nama_hari, hasil.seasonal.iloc[:7]):
print(f" {nama:8s} {nilai:+7.2f}")
print(f" Amplitudo musiman: {hasil.seasonal.max() - hasil.seasonal.min():.2f} unit")
residu = hasil.resid
masker_luar = (residu.index < pd.Timestamp("2026-03-16")) | (residu.index > pd.Timestamp("2026-03-29"))
masker_luar &= residu.index != pd.Timestamp("2026-04-20")
print(f"\nResidu: rata-rata {residu.mean():+.3f}, simpangan baku {residu.std():.2f}")
print(f"Residu pada hari diskon kilat (2026-04-20): {residu.loc['2026-04-20']:+.2f}")
print(f"Residu rata-rata selama kampanye 16-29 Mar: "
f"{residu.loc['2026-03-16':'2026-03-29'].mean():+.2f}")
print(f"Residu rata-rata di luar kampanye & diskon kilat: {residu[masker_luar].mean():+.2f}")Output yang Dihasilkan:
Nilai tren NaN di awal dan akhir deret: 3 dan 3 baris
Tren awal (2026-01-08): 129.34
Tren akhir (2026-06-18): 208.66
Lonjakan tren di tengah kampanye (di atas garis lurus): +45.46
Komponen musiman mingguan (unit):
Senin -8.60
Selasa -11.59
Rabu -14.45
Kamis -11.08
Jumat -4.49
Sabtu +19.49
Minggu +30.71
Amplitudo musiman: 45.16 unit
Residu: rata-rata +0.002, simpangan baku 11.56
Residu pada hari diskon kilat (2026-04-20): +69.36
Residu rata-rata selama kampanye 16-29 Mar: +4.64
Residu rata-rata di luar kampanye & diskon kilat: -0.91Mari kita telaah baris-baris kode yang berperan penting:
- Baris 27–28 (
seasonal_decompose(...)): pemanggilan denganmodel="additive"danperiod=7mengembalikan objek hasil yang menyimpan tiga komponen sebagaipandas.Seriesberindeks sama dengan deret asal, sehingga komponen dapat dipotong dan dibandingkan per tanggal. - Baris 30–34 (
hasil.trend): tren dihitung dengan rata-rata bergerak dua sisi (two-sided) sepanjang satu periode, sehingga tiga hari pertama dan terakhir bernilaiNaN— belum ada cukup tetangga di kedua sisi.dropna()membuang tepi tersebut sebelum dibaca. - Baris 36–40 (uji lonjakan tren): nilai tren pada 8 Maret dan 5 April dihubungkan garis lurus, lalu tren 22 Maret dibandingkan terhadap titik tengah garis tersebut. Lonjakan +45,46 hampir persis sebesar suntikan kampanye (+45): perubahan level selama dua minggu penuh diserap komponen tren, bukan residu.
- Baris 42–46 (
hasil.seasonal): komponen musiman berulang setiap tujuh hari, jadi tujuh nilai pertama merangkum seluruh pola. Hari kerja bernilai negatif dan akhir pekan positif dengan Minggu paling tinggi (+30,71) — amplitudo 45,16 unit mencerminkan jarak hari paling sepi ke paling ramai. - Baris 48–55 (
hasil.resid): residu adalah sisa setelah tren dan musiman dikurangkan. Rata-ratanya +0,002 — praktis nol, sifat yang diharapkan dari komponen sisa. Baris 51–52 menunjuk hari diskon kilat 20 April: residu +69,36 jauh melampaui simpangan baku 11,56, sehingga hari itu otomatis menonjol sebagai kandidat anomali.
Ketika Amplitudo Musiman Tumbuh: Model Multiplikatif #
Kategori produk yang sedang tumbuh pesat di Toko Ceria memberi contoh lawan dari deret pertama: level naik hampir tiga kali lipat dalam enam bulan dan lonjakan akhir pekan ikut membesar. Pada deret seperti ini, model aditif memaksakan amplitudo musiman yang tetap, sehingga pola yang tidak tertangkap menumpuk di residu. Contoh berikut membangun deret multiplikatif secara eksplisit, lalu membandingkan kedua model pada deret yang sama.
# Dekomposisi multiplikatif untuk deret yang amplitudo musimannya tumbuh
# seiring level: kategori produk yang sedang tumbuh pesat di Toko Ceria.
import numpy as np
import pandas as pd
from statsmodels.tsa.seasonal import seasonal_decompose
rng = np.random.default_rng(42)
n_hari = 168
idx = pd.date_range(start="2026-01-05", periods=n_hari, freq="D")
t = np.arange(n_hari)
hari = np.asarray(idx.dayofweek)
# Level tumbuh eksponensial dan lonjakan akhir pekan ikut membesar.
level = 120.0 * (1.0 + 0.006) ** t
faktor_hari = np.select([hari == 5, hari == 6], [1.28, 1.35], default=1.0)
derau_persen = rng.normal(1.0, 0.05, n_hari)
y = level * faktor_hari * derau_persen
penjualan = pd.Series(np.round(y, 2), index=idx, name="penjualan")
print(f"Rata-rata minggu pertama : {penjualan.iloc[:7].mean():7.2f}")
print(f"Rata-rata minggu terakhir : {penjualan.iloc[-7:].mean():7.2f}")
print(f"Selisih maksimum-minimum minggu pertama : {penjualan.iloc[:7].max() - penjualan.iloc[:7].min():7.2f}")
print(f"Selisih maksimum-minimum minggu terakhir : {penjualan.iloc[-7:].max() - penjualan.iloc[-7:].min():7.2f}")
hasil_mul = seasonal_decompose(penjualan, model="multiplicative", period=7)
hasil_add = seasonal_decompose(penjualan, model="additive", period=7)
nama_hari = ["Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"]
print("\nFaktor musiman mingguan (model multiplikatif):")
for nama, nilai in zip(nama_hari, hasil_mul.seasonal.iloc[:7]):
print(f" {nama:8s} {nilai:6.3f}")
print(f"\nResidu multiplikatif: rata-rata {hasil_mul.resid.mean():.4f}, "
f"simpangan baku {hasil_mul.resid.std():.4f}")
print(f"Residu aditif : rata-rata {hasil_add.resid.mean():+.2f}, "
f"simpangan baku {hasil_add.resid.std():.2f}")
rerata = penjualan.mean()
print(f"\nSimpangan baku residu relatif thd. rata-rata deret:")
print(f" multiplikatif: {hasil_mul.resid.std():.4f}")
print(f" aditif : {hasil_add.resid.std() / rerata:.4f}")Output yang Dihasilkan:
Rata-rata minggu pertama : 131.15
Rata-rata minggu terakhir : 354.47
Selisih maksimum-minimum minggu pertama : 58.07
Selisih maksimum-minimum minggu terakhir : 145.41
Faktor musiman mingguan (model multiplikatif):
Senin 0.917
Selasa 0.921
Rabu 0.909
Kamis 0.920
Jumat 0.907
Sabtu 1.172
Minggu 1.253
Residu multiplikatif: rata-rata 0.9999, simpangan baku 0.0396
Residu aditif : rata-rata -0.12, simpangan baku 12.65
Simpangan baku residu relatif thd. rata-rata deret:
multiplikatif: 0.0396
aditif : 0.0564Bedah kode di atas menyoroti empat kelompok baris:
- Baris 8–19 (pembangunan deret): level tumbuh 0,6 persen per hari secara eksponensial, faktor akhir pekan 1,28 dan 1,35 mengalikan level (bukan menambah), dan derau berbentuk persentase (rata-rata 1,0, simpangan 0,05). Seluruh komponen bergabung lewat perkalian — definisi operasional deret multiplikatif.
- Baris 21–24 (diagnosis amplitudo): rata-rata mingguan naik dari 131,15 ke 354,47, dan rentang dalam satu minggu melebar dari 58,07 ke 145,41. Amplitudo yang tumbuh seiring level adalah petunjuk visual terkuat bahwa model aditif tidak lagi cocok.
- Baris 29–32 (faktor musiman): komponen musiman model multiplikatif berupa faktor pengali relatif terhadap tren. Hari kerja berkumpul di sekitar 0,91, Sabtu 1,172, dan Minggu 1,253 — perbedaan kecil antar hari kerja mencerminkan derau, bukan pola.
- Baris 34–41 (adu model): residu model multiplikatif bersimpangan baku 0,0396 (rata-rata 0,9999 — residu multiplikatif idealnya berpusat di 1,0). Dibandingkan pada skala relatif terhadap rata-rata deret, residu model aditif (0,0564) sekitar 42 persen lebih besar daripada model multiplikatif (0,0396) — model yang salah bentuk meninggalkan jejak pada residu.
Dekomposisi memberi kita kacamata untuk membaca struktur deret, dan salah satu temuannya sangat menentukan bagi chapter berikutnya: tren membuat rata-rata penjualan terus bergerak, dari 129 ke 209 dalam enam bulan. Model ARIMA klasik tidak siap bekerja pada deret yang rata-ratanya berjalan — deret harus dibuat stationary lebih dulu, dan kestabilan itu perlu diuji, bukan dikira. Kedua hal tersebut adalah materi Chapter Stationaritas dan Uji Augmented Dickey-Fuller.
Referensi Resmi #
- statsmodels —
seasonal_decompose— API resmi fungsi dekomposisi beserta parameterperiod,model,two_sided, danextrapolate_trend. - statsmodels — Time Series Analysis by State Space Methods
— halaman modul
tsayang menghimpun seluruh alat deret waktu pada statsmodels.