Dekomposisi pada chapter sebelumnya memperlihatkan tren yang menanjak dari 129 ke 209 unit — rata-rata penjualan Toko Ceria terus bergerak sepanjang enam bulan. Model ARIMA klasik yang akan kita rakit pada chapter berikutnya mengasumsikan kebalikan dari kondisi tersebut: deret yang stabil secara statistik, atau stationary. Untungnya, kestabilan bukan tebakan — deret dapat diuji secara formal dengan Augmented Dickey-Fuller (ADF), dan bila belum stabil, operasi sederhana bernama differencing biasanya menyelesaikannya. Chapter ini membahas definisi stationaritas, mekanika uji ADF beserta cara membaca keluarannya, dan uji pendamping KPSS yang hipotesis nolnya sengaja dibalik.
Apa Itu Stationaritas #
Deret dikatakan stationaritas lemah (weakly stationary) bila tiga sifatnya tidak berubah sepanjang waktu: rata-ratanya konstan, variansnya konstan, dan autokovariansinya hanya bergantung pada jarak lag — bukan pada posisi kalender. Penjualan Toko Ceria melanggar syarat pertama karena tren menaikkan rata-rata dari 158,05 pada paruh pertama menjadi 187,77 pada paruh kedua. Penjualan juga melanggar syarat ketiga: hubungan nilai Senin ini dengan Senin depan berbeda kekuatannya di awal dan akhir deret karena keduanya berdiri di level yang berbeda.
Mengapa ARIMA membutuhkan stationaritas? Model ARIMA memperkirakan sekumpulan koefisien yang dianggap berlaku untuk seluruh deret — “pengaruh nilai kemarin terhadap hari ini” diasumsikan sama di Januari maupun Juni. Pada deret yang rata-ratanya berjalan, asumsi tersebut tidak berlaku: satu set koefisien dipaksa merangkum relasi yang terus bergeser, sehingga taksiran menjadi bias dan ramalan jangka panjang melebar tanpa dasar. Model yang benar-benar memodelkan tren secara eksplisit (misalnya dengan parameter d untuk differencing) bekerja pada deret yang telah distabilkan lebih dulu.
Diferencing: Menstabilkan Rata-rata #
Differencing mengambil selisih antar-pengamatan berurutan: z_t = y_t - y_(t-1). Tren linear +0,45 unit per hari terhapus otomatis dari hasil selisih — setiap nilai hanya menyisakan efek hari, kampanye, dan derau. Pada pandas, operasi ini dilakukan Series.diff(); pada NumPy, np.diff() memberi hasil yang identik. Satu kali differencing menaikkan parameter d pada ARIMA satu tingkat; deret yang bergerak lebih rumit (misalnya tren melengkung) kadang perlu d = 2, tetapi kasus tersebut jarang.
Uji Augmented Dickey-Fuller #
ADF menguji hipotesis nol bahwa deret memiliki akar unit — penyebab matematis non-stationaritas pada rata-rata. Versi augmented meregresikan perubahan hari ini terhadap nilai kemarin serta beberapa perubahan lampau sebagai kontrol, lalu menguji apakah koefisien nilai kemarin benar-benar nol. Fungsi adfuller dari statsmodels.tsa.stattools mengembalikan sebuah tuple berisi statistik uji, nilai p, jumlah lag terpakai, jumlah observasi efektif, kamus nilai kritis, dan kriteria informasi; autolag="AIC" memilih jumlah lag kontrol secara otomatis. Contoh berikut membaca seluruh keluaran tersebut untuk deret asli dan deret diferensi.
# Stationaritas: membandingkan statistik paruh awal-akhir, lalu menguji
# deret asli dan deret diferensi dengan Augmented Dickey-Fuller (ADF).
import numpy as np
import pandas as pd
from statsmodels.tsa.stattools import adfuller
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()
paruh = len(penjualan) // 2
awal = penjualan.iloc[:paruh]
akhir = penjualan.iloc[paruh:]
print(f"Paruh pertama: rata-rata {awal.mean():7.2f} simpangan baku {awal.std():6.2f}")
print(f"Paruh kedua : rata-rata {akhir.mean():7.2f} simpangan baku {akhir.std():6.2f}")
diferensi = penjualan.diff().dropna()
print("\nLima nilai diferensi pertama (pandas .diff()):")
print(" " + " ".join(f"{v:+.1f}" for v in diferensi.head(5).to_numpy()))
print("Lima nilai diferensi pertama (numpy np.diff):")
print(" " + " ".join(f"{v:+.1f}" for v in np.diff(penjualan.to_numpy())[:5]))
def ringkas_adf(deret, nama):
stat, pnilai, lag, nobs, kritis, _ = adfuller(deret, autolag="AIC")
print(f"\nADF {nama}:")
print(f" statistik = {stat:8.3f} p-value = {pnilai:.4g}")
print(f" lag terpakai = {lag} jumlah observasi = {nobs}")
for tingkat in ("1%", "5%", "10%"):
print(f" nilai kritis {tingkat:>3s}: {kritis[tingkat]:7.3f}")
return pnilai
p_asli = ringkas_adf(penjualan, "deret asli ")
p_diff = ringkas_adf(diferensi, "deret diferensi")
print("\nKesimpulan (taraf signifikansi 5%):")
if p_asli >= 0.05:
print(" deret asli : gagal menolak H0 -> terdapat akar unit (tidak stationar)")
if p_diff < 0.05:
print(" deret diferensi : menolak H0 -> tidak ada akar unit (stationar)")Output yang Dihasilkan:
Paruh pertama: rata-rata 158.05 simpangan baku 31.10
Paruh kedua : rata-rata 187.77 simpangan baku 25.56
Lima nilai diferensi pertama (pandas .diff()):
-15.7 +21.9 +2.7 -24.2 +30.2
Lima nilai diferensi pertama (numpy np.diff):
-15.7 +21.9 +2.7 -24.2 +30.2
ADF deret asli :
statistik = -1.619 p-value = 0.4732
lag terpakai = 8 jumlah observasi = 159
nilai kritis 1%: -3.472
nilai kritis 5%: -2.880
nilai kritis 10%: -2.577
ADF deret diferensi:
statistik = -5.121 p-value = 1.273e-05
lag terpakai = 12 jumlah observasi = 154
nilai kritis 1%: -3.474
nilai kritis 5%: -2.880
nilai kritis 10%: -2.577
Kesimpulan (taraf signifikansi 5%):
deret asli : gagal menolak H0 -> terdapat akar unit (tidak stationar)
deret diferensi : menolak H0 -> tidak ada akar unit (stationar)Mari kita telaah bagian-bagian penting dari kode di atas:
- Baris 27–32 (diagnosis kasar): membandingkan rata-rata dan simpangan baku paruh pertama dan kedua memberi pemeriksaan cepat tanpa model. Rata-rata yang melompat dari 158,05 ke 187,77 sudah cukup sebagai dugaan kuat non-stationaritas; simpangan baku yang justru mengecil (31,10 menjadi 25,56) menunjukkan sumber ketidakstabilan utamanya memang rata-rata, bukan sebaran.
- Baris 34–38 (differensi):
penjualan.diff()menghasilkan deret selisih;.dropna()membuang satu nilaiNaNdi awal karena hari pertama tidak punya hari sebelumnya. Lima nilai pertama.diff()dannp.diff()tercetak sama persis — dua jalur, satu hasil. - Baris 41–48 (membaca tuple ADF): fungsi
ringkas_adfmembongkar keluaranadfullersatu per satu. Statistik uji dibandingkan dengan nilai kritis pada tiga taraf; makin negatif statistiknya, makin kuat bukti melawan akar unit. Elemen keenam tuple (kriteria informasi pemilih lag) diabaikan dengan_. - Baris 51–58 (keputusan): pada deret asli, statistik −1,619 tidak lebih negatif daripada nilai kritis 5% (−2,880) dan nilai p 0,4732 jauh di atas 0,05 — ADF gagal menolak hipotesis akar unit. Pada deret diferensi, statistik −5,121 menembus nilai kritis 1% (−3,474) dengan nilai p sekitar 0,0000127 — bukti kuat bahwa deret hasil differencing sudah stationar.
KPSS: Uji Pelengkap dengan Hipotesis Terbalik #
ADF bukan satu-satunya hakim. Uji KPSS (Kwiatkowski–Phillips–Schmidt–Shin) membalik arah hipotesis: hipotesis nolnya adalah deret stationar, sehingga nilai p kecil berarti bukti terhadap stationaritas. Menjalankan dua uji dengan hipotesis berlawanan saling mengonfirmasi: hasil yang sama dari keduanya jauh lebih meyakinkan daripada satu uji tunggal. Contoh berikut menjalankan keduanya pada deret asli dan deret diferensi Toko Ceria.
# KPSS sebagai uji pelengkap ADF: hipotesis nolnya dibalik (deret stationar).
# Kedua uji dijalankan pada deret asli dan deret diferensi Toko Ceria.
import warnings
import numpy as np
import pandas as pd
from statsmodels.tsa.stattools import adfuller, kpss
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()
diferensi = penjualan.diff().dropna()
print("Hipotesis nol ADF : deret memiliki akar unit (tidak stationar)")
print("Hipotesis nol KPSS: deret stationar")
print("Kedua p-value KPSS dibatasi tabel pada 0.01 dan 0.10.")
# Nilai-p KPSS di tepi tabel memicu InterpolationWarning; keputusan
# tolak/tidak-tolak tetap sah, jadi peringatan kita saring saja.
with warnings.catch_warnings():
warnings.simplefilter("ignore")
hasil_kpss = {
"deret asli ": kpss(penjualan, regression="c", nlags="auto"),
"deret diferensi": kpss(diferensi, regression="c", nlags="auto"),
}
for nama, (stat, pnilai, lag, kritis) in hasil_kpss.items():
print(f"\nKPSS {nama.strip()}:")
print(f" statistik = {stat:6.3f} p-value = {pnilai:.3f} lag = {lag}")
for tingkat in ("1%", "2.5%", "5%", "10%"):
print(f" nilai kritis {tingkat:>3s}: {kritis[tingkat]:7.3f}")
p_adf_asli = adfuller(penjualan, autolag="AIC")[1]
p_adf_diff = adfuller(diferensi, autolag="AIC")[1]
p_kpss_asli = hasil_kpss["deret asli "][1]
p_kpss_diff = hasil_kpss["deret diferensi"][1]
print("\nRingkasan empat keputusan (taraf 5%):")
baris = [
("deret asli", p_adf_asli, p_kpss_asli),
("deret diferensi", p_adf_diff, p_kpss_diff),
]
for nama, p_adf, p_kpss in baris:
adf_stat = "menolak H0" if p_adf < 0.05 else "gagal menolak H0"
kpss_stat = "menolak H0" if p_kpss < 0.05 else "gagal menolak H0"
print(f" {nama:15s} ADF: {adf_stat:18s} KPSS: {kpss_stat}")
print("\nKedua uji sepakat: deret asli tidak stationar, deret diferensi stationar.")Output yang Dihasilkan:
Hipotesis nol ADF : deret memiliki akar unit (tidak stationar)
Hipotesis nol KPSS: deret stationar
Kedua p-value KPSS dibatasi tabel pada 0.01 dan 0.10.
KPSS deret asli:
statistik = 1.730 p-value = 0.010 lag = 7
nilai kritis 1%: 0.739
nilai kritis 2.5%: 0.574
nilai kritis 5%: 0.463
nilai kritis 10%: 0.347
KPSS deret diferensi:
statistik = 0.154 p-value = 0.100 lag = 50
nilai kritis 1%: 0.739
nilai kritis 2.5%: 0.574
nilai kritis 5%: 0.463
nilai kritis 10%: 0.347
Ringkasan empat keputusan (taraf 5%):
deret asli ADF: gagal menolak H0 KPSS: menolak H0
deret diferensi ADF: menolak H0 KPSS: gagal menolak H0
Kedua uji sepakat: deret asli tidak stationar, deret diferensi stationar.Bedah kode di atas menyoroti tiga kelompok baris:
- Baris 38–43 (
warnings.catch_warnings): nilai-p KPSS dihitung dari interpolasi tabel yang hanya mencakup 0,01 sampai 0,10, sehingga hasil di tepi tabel memicuInterpolationWarning. Keputusan tolak/tidak-tolak tetap sah; peringatan kita saring agar keluaran bersih — praktik yang wajar untuk laporan otomatis. - Baris 45–49 (membaca keluaran KPSS): pada deret asli, statistik 1,730 jauh melampaui nilai kritis 1% (0,739) — hipotesis nol stationaritas ditolak dengan sangat kuat. Pada deret diferensi, statistik 0,154 duduk di bawah seluruh nilai kritis, dan nilai p menyentuh batas atas tabel 0,10: KPSS tidak menemukan bukti melawan stationaritas.
- Baris 56–65 (ringkasan silang): keempat keputusan disandingkan — ADF gagal menolak + KPSS menolak pada deret asli, ADF menolak + KPSS gagal menolak pada deret diferensi. Kedua uji yang hipotesisnya berlawanan menghasilkan cerita yang konsisten satu sama lain.
Bagaimana bila kedua uji justru berselisih? Kombinasi keputusannya sendiri sudah menjadi diagnosis:
| Keputusan ADF | Keputusan KPSS | Tafsiran umum | Tindakan yang lazim |
|---|---|---|---|
| Gagal menolak | Menolak | Non-stationar (akar unit) | Differencing, uji ulang |
| Menolak | Gagal menolak | Stationar | Lanjut ke pemodelan |
| Menolak | Menolak | Trend-stationary (tren deterministik) | Detrending atau ADF dengan regression="ct" |
| Gagal menolak | Gagal menolak | Struktur berubah / segmen berbeda | Periksa perubahan struktural, transformasi |
Kita kini memiliki resep yang tuntas untuk menyiapkan bahan baku ARIMA: transformasi bila sebaran melebar, differencing sampai ADF dan KPSS bersepakat, lalu berhenti sebelum berlebihan. Deret diferensi Toko Ceria yang telah lulus uji adalah bahan mentah yang siap dirakit menjadi model peramal. Proses perakitan itu — memilih p, d, dan q, membaca ACF/PACF, dan mengadu hasilnya dengan baseline sederhana — adalah materi Chapter ARIMA untuk Forecasting.
Referensi Resmi #
- statsmodels —
adfuller— API resmi uji Augmented Dickey-Fuller beserta makna setiap elemen tuple keluarannya. - statsmodels —
kpss— API resmi uji KPSS, uji pelengkap dengan hipotesis nol stationaritas. - statsmodels — Stationarity and detrending (ADF/KPSS) — contoh resmi memakai kedua uji secara berdampingan, termasuk kasus hasil yang bertentangan.