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.

python
# 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:

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

  1. 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.
  2. Baris 34–38 (differensi): penjualan.diff() menghasilkan deret selisih; .dropna() membuang satu nilai NaN di awal karena hari pertama tidak punya hari sebelumnya. Lima nilai pertama .diff() dan np.diff() tercetak sama persis — dua jalur, satu hasil.
  3. Baris 41–48 (membaca tuple ADF): fungsi ringkas_adf membongkar keluaran adfuller satu 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 _.
  4. 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.

python
# 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:

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

  1. 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 memicu InterpolationWarning. Keputusan tolak/tidak-tolak tetap sah; peringatan kita saring agar keluaran bersih — praktik yang wajar untuk laporan otomatis.
  2. 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.
  3. 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 ADFKeputusan KPSSTafsiran umumTindakan yang lazim
Gagal menolakMenolakNon-stationar (akar unit)Differencing, uji ulang
MenolakGagal menolakStationarLanjut ke pemodelan
MenolakMenolakTrend-stationary (tren deterministik)Detrending atau ADF dengan regression="ct"
Gagal menolakGagal menolakStruktur berubah / segmen berbedaPeriksa 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 #