IlmHamroh
Data Science va sun'iy intellekt/Maxsus mavzular2/12-dars47 daqiqa
Mundarija (26)

28.2-dars: Klassik bashorat modellari

28-QISM — MAXSUS MAVZULAR · 2-dars


1. Kirish va motivatsiya

Oldingi darsda vaqt qatorining tuzilishini (trend, mavsum, bayramlar), statsionarlikni, ACF/PACF ni va — eng muhimi — halol baholashni ko'rdik: faqat o'tmishda o'qitib kelajakda sinash, rolling-origin backtest, bazaviylar va "eng yaxshisidan sezilarli yomon bo'lmagan eng sodda" qoidasi. Endi shu poydevor ustiga vaqt qatorlari uchun maxsus yaratilgan klassik modellar quramiz.

Ular ikki oilaga bo'linadi. Eksponensial silliqlash (SES, Holt, Holt-Winters, umumiy ko'rinishi — ETS) "yaqin o'tmish uzoq o'tmishdan muhimroq" degan oddiy g'oyaga asoslanadi: daraja, trend va mavsum har yangi kuzatuvdan keyin ozgina yangilanadi. ARIMA oilasi esa qatorni farqlab statsionar qiladi va uning avtokorrelyatsiyasini AR va MA qismlari bilan tasvirlaydi; SARIMA mavsumiy qismni, ARIMAX esa tashqi o'zgaruvchilarni (bayram, narx, ob-havo) qo'shadi. Bu modellar 1950-1970-yillarda yaratilgan, lekin hozir ham ishlab chiqarishda keng ishlatiladi: tez, kam ma'lumot bilan ishlaydi, tushunarli va — ko'pincha — ancha murakkab modellar bilan teng raqobatlashadi.

Real vaziyat. Viloyat elektr tarmoqlari kompaniyasi har oy keyingi yarim yil uchun iste'molni bashorat qiladi: shunga qarab qo'shni davlatlardan elektr sotib olish shartnomasi tuziladi. Tahlilchi SARIMA ni o'qitdi va "95% ishonch intervali" bilan hisobot berdi. Rahbar intervalning yuqori chegarasiga qarab zaxira sotib oldi. Bir yil ichida olti oydan ikkitasida haqiqiy iste'mol intervaldan chiqib ketdi — "95%" aslida taxminan 80% ekan. Bundan tashqari, Ramazon oylari har yili ~11 kun oldinga siljigani uchun oylik mavsum uni ushlay olmas edi. Bu darsda aynan shu ikki savolni o'lchaymiz: interval va'da qilganini beradimi, tashqi o'zgaruvchi qanchalik yordam beradi.

Bu darsda klassik modellarni noldan tushunib, statsmodels bilan qurishni, tartibni tanlashni, qoldiqlarni tekshirishni va modellarni seasonal naive bilan halol, juftlashgan backtestda solishtirishni o'rganamiz.

Bu darsda:

  • Eksponensial silliqlash: SES (noldan), Holt, damped trend, Holt-Winters, ETS tizimi
  • ARIMA(p, d, q): AR va MA qismlari, farqlash, bashorat xulqi
  • SARIMA: mavsumiy qism, "airline" modeli
  • Tartibni tanlash: ACF/PACF qoidalari va AIC/BIC setkasi; AIC ni faqat bir xil d ichida solishtirish
  • Qoldiqlar diagnostikasi: Ljung-Box
  • Bashorat intervallari va ularning haqiqiy qamrovi
  • ARIMAX: tashqi o'zgaruvchilar (Ramazon kunlari)
  • Rolling-origin backtest: seasonal naive, ETS, SARIMA, ARIMAX; vaqt byudjeti
  • Prophet — g'oyasi (nazariyada)

ℹ Misollar real numpy/pandas/scipy/statsmodels bilan (Python 3.14). Ma'lumot sintetik, urug' bilan yaratiladi; "Ramazon kunlari" — sintetik taqvimda har yili ~11 kun oldinga siljiydigan 30 kunlik davr.


2. Nazariya — chuqur tushuntirish

2.1. Oddiy eksponensial silliqlash (SES)

Ikki bazaviyni eslang: naive faqat oxirgi kuzatuvga ishonadi, o'rtacha — hamma kuzatuvga teng. SES ularning o'rtasida: yaqin kuzatuvlarga ko'proq, uzoqlariga geometrik kamayuvchi vazn beradi.

text
DARAJA YANGILANISHI:
  l_t = alfa * y_t + (1 - alfa) * l_{t-1}          0 < alfa < 1
      = l_{t-1} + alfa * (y_t - l_{t-1})           "xatoning alfa ulushicha tuzat"

BASHORAT:   f_{T+h} = l_T      (har qanday h uchun - TEKIS chiziq)

VAZNLAR (ochib yozsak):
  l_T = alfa*y_T + alfa(1-alfa)*y_{T-1} + alfa(1-alfa)^2*y_{T-2} + ...
  alfa = 0.2:  0.200  0.160  0.128  0.102  ...  (uzoq xotira, silliq)
  alfa = 0.6:  0.600  0.240  0.096  0.038  ...  (qisqa xotira, tez moslashadi)
  alfa -> 1: naive;   alfa -> 0: boshlang'ich daraja (o'rtachaga yaqin)

PARAMETRLAR: alfa va l_0 - bir qadamli xatolar kvadratlari yig'indisini
  (SSE) minimallashtirib topiladi

SES faqat trendsiz, mavsumsiz qator uchun: masalan, ehtiyot qismga bo'lgan talab, u sekin sayr qiladi va shovqinli. Trend yoki mavsum bo'lsa, SES doim orqada qoladi.

2.2. Holt, damped trend va Holt-Winters

Holt darajaga trend (qiyalik) holatini qo'shadi, Holt-Winters esa mavsum holatini:

text
HOLT (chiziqli trend):
  l_t = alfa * y_t + (1 - alfa) * (l_{t-1} + b_{t-1})
  b_t = beta * (l_t - l_{t-1}) + (1 - beta) * b_{t-1}
  f_{T+h} = l_T + h * b_T                   <- trend CHEKSIZ davom etadi

DAMPED (so'nuvchi) TREND:  0 < phi < 1
  f_{T+h} = l_T + (phi + phi^2 + ... + phi^h) * b_T
  uzoq ufqda trend "tekislanadi" - amalda ko'pincha xavfsizroq

HOLT-WINTERS (mavsum m):
  qo'shiluvchi:     f_{T+h} = l_T + h*b_T + s_{T+h-m}
  ko'paytiruvchi:   f_{T+h} = (l_T + h*b_T) * s_{T+h-m}
  s_t = gamma * (y_t - l_t) + (1 - gamma) * s_{t-m}       (qo'shiluvchi)
  gamma = 0 -> mavsum profili O'ZGARMAS; gamma katta -> tez o'zgaradi

28.1-darsdagi qoida bu yerda ham ishlaydi: mavsum amplitudasi daraja bilan o'ssa — ko'paytiruvchi Holt-Winters yoki log qatorda qo'shiluvchi. Ikkinchi yo'lning afzalligi — ETS va ARIMA bilan bir xil fazoda ishlaysiz va log ning xatolari foizli ma'noga ega.

2.3. ETS tizimi: xato, trend, mavsum

Eksponensial silliqlash modellari umumiy ETS (Error, Trend, Seasonal) tizimiga yig'iladi:

text
ETS(E, T, S):
  E - xato:    A (qo'shiluvchi) | M (ko'paytiruvchi)
  T - trend:   N (yo'q) | A (chiziqli) | Ad (so'nuvchi)
  S - mavsum:  N | A | M

  ETS(A,N,N) = SES          ETS(A,A,N) = Holt
  ETS(A,Ad,A) = so'nuvchi trend + qo'shiluvchi mavsum (ko'pincha kuchli sukut)

NIMA BERADI:
  holat fazosi (state space) ko'rinishi -> haqiqiy LIKELIHOOD
  -> AIC bilan model tanlash (bir xil ma'lumotda!)
  -> model asosidagi BASHORAT INTERVALLARI
  xato turi (A yoki M) nuqtali bashoratni deyarli o'zgartirmaydi,
  lekin intervallar va likelihood ni o'zgartiradi

statsmodels da ikki API bor: holtwinters.ExponentialSmoothing (SSE bo'yicha, tez, intervalsiz) va exponential_smoothing.ets.ETSModel (likelihood, AIC, get_prediction(...).summary_frame() bilan intervallar).

2.4. ARIMA(p, d, q)

ARIMA — uch g'oyaning birikmasi. Orqaga siljitish operatori B y_t = y_{t-1} bilan yozish qulay:

text
AR(p) - AVTOREGRESSIYA: qator o'z o'tmishiga bog'liq
  y_t = c + phi_1*y_{t-1} + ... + phi_p*y_{t-p} + e_t
  AR(1), |phi| < 1: shokdan keyin o'rtachaga GEOMETRIK qaytadi

MA(q) - SIRPANUVCHI O'RTACHA (xatolar bo'yicha): o'tgan SHOKLARGA bog'liq
  y_t = c + e_t + theta_1*e_{t-1} + ... + theta_q*e_{t-q}
  shok faqat q qadam "eslanadi", keyin butunlay unutiladi

I(d) - INTEGRATSIYA: d marta farqlash
  d = 1:  y'_t = y_t - y_{t-1}  ga ARMA qo'llanadi

ARIMA(p,d,q):  (1 - phi_1 B - ... - phi_p B^p) (1 - B)^d y_t
                  = c + (1 + theta_1 B + ... + theta_q B^q) e_t

BASHORAT XULQI (uzoq ufqda):
  d = 0, c bor  -> qator o'rtachasiga qaytadi
  d = 1, c yo'q -> tekis chiziq (tasodifiy yurish kabi)
  d = 1, c bor  -> chiziqli trend (drift)
  d = 2         -> trend ham o'zgaradi (xavfli ekstrapolyatsiya)
  intervallar: d = 0 da cheklangan kenglikka yetadi, d >= 1 da cheksiz o'sadi

ARIMA(0,1,1) va SES bir xil bashorat beradi (theta = alfa - 1), ARIMA(0,2,2) esa Holt ga mos — ikki oila qisman ustma-ust tushadi.

2.5. SARIMA: mavsumiy qism

text
SARIMA(p,d,q)(P,D,Q)_m:
  mavsumiy AR:   (1 - Phi_1 B^m - ...)         "o'tgan yilning shu oyi"
  mavsumiy farq: (1 - B^m)^D                   y_t - y_{t-12}
  mavsumiy MA:   (1 + Theta_1 B^m + ...)       "o'tgan yilning shu oyidagi shok"
  hammasi oddiy qism bilan KO'PAYTIRILADI

"AIRLINE" MODELI:  SARIMA(0,1,1)(0,1,1)_12  (log qatorda)
  (1 - B)(1 - B^12) log y_t = (1 + theta B)(1 + Theta B^12) e_t
  faqat 2 parametr; oylik biznes qatorlari uchun klassik boshlang'ich nuqta

ESLATMA: m katta bo'lsa (kunlik ma'lumot + yillik mavsum, m = 365)
  SARIMA juda sekin va beqaror -> Fourier belgilar bilan ARIMAX yoki ML (28.3)

2.6. Tartibni tanlash: ACF/PACF va AIC

1-qadam: d va D. 28.1-darsdagi kabi: log (kerak bo'lsa), mavsum bo'lsa D = 1, keyin ADF/KPSS va ACF bilan kerak bo'lsa d = 1. Odatda d + D <= 2.

2-qadam: p, q, P, Q. Farqlangan qatorning ACF/PACF ga qarab boshlang'ich taxmin (28.1, 2.6-bo'lim):

text
ACF 1-lagda cho'qqi, keyin uziladi        -> q = 1
PACF 1..p lagda cho'qqi, keyin uziladi    -> p
ACF 12-lagda manfiy cho'qqi               -> Q = 1 (mavsumiy MA)
PACF 12, 24-laglarda cho'qqi              -> P = 1

3-qadam: kichik AIC setkasi atrofida:

text
AIC = -2 * log L + 2 * k          (k - parametrlar soni)
BIC = -2 * log L + k * ln(n)      (kattaroq jarima -> soddaroq model)
dAIC < 2   - modellar deyarli teng -> soddasini oling

AIC ni faqat bir xil ma'lumotda solishtirish mumkin. Likelihood — ma'lum ma'lumotning ehtimoli. Farqlash likelihood hisoblanadigan ma'lumotni o'zgartiradi: d = 1 modelda birinchi kuzatuv "boshlang'ich holat" bo'lib, likelihood dan chiqariladi (statsmodels da loglikelihood_burn = 1), D = 1 da yana 12 ta. Natijada d = 0 va d = 1 modellarining AIC lari turli miqdordagi hadlar yig'indisi — ularning farqi hatto o'lchov birligiga bog'liq bo'lib qoladi (GWh yoki MWh!). 2-misolda buni raqam bilan ko'ramiz. Xuddi shu sabab bilan y va log y da o'qitilgan modellarning AIC larini ham to'g'ridan-to'g'ri solishtirib bo'lmaydi. d ni testlar va domen bilimi bilan, shubhada esa backtest bilan tanlang; AIC — faqat bir xil d, D va bir xil almashtirish ichida.

2.7. Qoldiqlar diagnostikasi

Yaxshi model qatordagi barcha bashorat qilinadigan tuzilishni "yeydi" — qoldiqlar oq shovqinga o'xshashi kerak.

text
LJUNG-BOX testi (H0: 1..L laglarda avtokorrelyatsiya yo'q):
  Q = n(n+2) * sum_{k=1..L} r_k^2 / (n - k)   ~  chi^2 (L - model_df)
  model_df = p + q + P + Q     (baholangan ARMA parametrlari soni)
  L: oddiy qator uchun ~10, mavsumiy uchun 2m (24)

p < 0.05  -> qoldiqda tuzilish QOLGAN: tartibni oshiring, tashqi
             o'zgaruvchi qo'shing yoki almashtirishni o'zgartiring
p katta   -> "tuzilish topilmadi" (isbot emas - quvvat cheklangan)

QO'SHIMCHA: qoldiq ACF grafigi, qoldiq vs vaqt (dispersiya o'zgaradimi?),
qoldiqlarni tashqi omillar bo'yicha guruhlash (bayram oylari!)

Diqqat: farqlangan modelda birinchi d + D*m qoldiq boshlang'ich holatdan kelib chiqqan "sun'iy" qiymatlar — ularni diagnostikadan tashlang.

2.8. Bashorat intervallari va haqiqiy qamrov

ETS va ARIMA model asosidagi intervallarni beradi: f +- 1.96 * sigma_h, bu yerda sigma_h ufq bilan o'sadi. Lekin bu formula uchta farazga tayanadi:

text
1. MODEL TO'G'RI (tuzilish, tartib, mavsum turi)
2. PARAMETRLAR ANIQ MA'LUM (baholash xatosi hisobga olinmaydi)
3. XATOLAR NORMAL va dispersiyasi O'ZGARMAS

amalda uchalasi ham buziladi -> intervallar odatda TOR
"95%" interval backtestda 80-90% qamrashi odatiy hol

Shuning uchun interval ham nuqtali bashorat kabi o'lchanadi: backtestning barcha oynalarida haqiqiy qiymat intervalga necha marta tushdi? Bu empirik qamrov. Uni nominal 0.95-bob bilan solishtiramiz; kenglikni ham ko'rsatamiz — keng interval qamrovni oson oshiradi, lekin foydasiz. Qamrovning noaniqligini unutmang: 60 nuqtada binomial SE sqrt(0.95 * 0.05 / 60) ~ 0.028, bir oynaning nuqtalari esa bir-biriga bog'liq, shuning uchun haqiqiy noaniqlik bundan katta. Log qatorda qurilgan intervalni exp bilan asl birlikka qaytarish mumkin (kvantillar monoton almashtirishda saqlanadi), lekin exp(o'rtacha) — o'rtacha emas, mediana bashorati.

28.3-darsda model farazlariga tayanmaydigan konformal intervallarni quramiz — ular qamrovni tarixiy xatolar bo'yicha kalibrlaydi.

2.9. Backtest, qaror va vaqt byudjeti

28.1-darsdagi retsept o'zgarmaydi: bir xil oynalar, juftlashgan farq, oynalar bo'yicha SE, soddalik tartibi oldindan yoziladi. Bu darsda tartib:

text
seasonal naive  <  ETS(A,Ad,A)  <  SARIMA  <  ARIMAX
(0 parametr)       (4 silliqlash    (3 ARMA     (+ exog koeffitsienti
                    parametri +      parametri   va tashqi ma'lumot
                    boshl. holat)    + d, D)     quvuri)

ARIMAX ning "murakkabligi" faqat parametrlar soni emas: tashqi o'zgaruvchi kelajakda ham kerak, ya'ni uning ham ishonchli manbasi va quvuri bo'lishi kerak.

Vaqt byudjeti. SARIMA fit — iterativ optimizatsiya, ancha sekin (mavsumiy modelda bir necha soniya bo'lishi mumkin). Backtestda oynalar x modellar x (setka) marta chaqiriladi. Amaliy usullar:

text
1. PARAMETRLARNI KAMROQ QAYTA BAHOLASH
   har N oynada fit, oraliqda: natija.apply(yangi_y)  (refit yo'q,
   faqat holat yangilanadi - juda tez)
2. ISSIQ START:  fit(start_params=oldingi.params) - iteratsiyalar kamayadi
3. SETKANI KICHRAYTIRISH: tartib bir marta (birinchi oynalarda) tanlanadi,
   backtest davomida qotiriladi
4. OYNALAR SONINI HISOBLAB TANLANG:
   10 oyna x 2 model x 1 s = 20 s;  60 oyna x setka 16 x 1 s = 16 daqiqa

2.10. ARIMAX: tashqi o'zgaruvchilar

text
REGRESSIYA + ARIMA XATOLAR:
  y_t = beta * x_t + u_t,     u_t ~ ARIMA(p,d,q)(P,D,Q)_m
  x_t: bayram kunlari soni, narx, aksiya, harorat, ish kunlari soni

SHART: x ning KELAJAKDAGI qiymatlari bashorat paytida MA'LUM bo'lishi kerak
  taqvim (bayram, Ramazon, ish kunlari)  -> aniq ma'lum        ✅
  rejalashtirilgan aksiya, narx           -> reja bo'yicha      ✅
  harorat, raqobatchi narxi                -> O'ZI bashorat      ⚠️
     (backtestda HAQIQIY harorat bilan sinasangiz - bu sizish:
      ishlab chiqarishda sizda faqat ob-havo BASHORATI bo'ladi)

Harakatlanuvchi bayramlar (Ramazon hayiti, Qurbon hayiti) klassik tashqi o'zgaruvchi: oylik mavsum ularni ushlay olmaydi, chunki ular har yili boshqa oyga tushadi.

2.11. Prophet — g'oyasi (nazariyada)

Prophet (Meta) — biznes qatorlari uchun mashhur kutubxona. Bu muhitda o'rnatilmagan, shuning uchun faqat g'oyasini ko'ramiz:

text
y(t) = g(t) + s(t) + h(t) + e_t        (qo'shiluvchi regressiya, ARIMA emas)
  g(t) - bo'lakli chiziqli trend; o'zgarish nuqtalari (changepoints) avtomatik,
         ularning soni regulyarizatsiya bilan cheklanadi
  s(t) - mavsumlar Fourier qatorlari bilan: sum a_k cos(2 pi k t / P) + b_k sin(...)
         (haftalik P = 7, yillik P = 365.25 - bir vaqtda bir nechta)
  h(t) - bayramlar ro'yxati (sana + oldin/keyin oynasi)
  intervallar: trend o'zgarishlarini simulyatsiya qilib

KUCHLI: ko'p mavsum, bayramlar, bo'sh sanalar, tushunarli komponentlar
ZAIF:   avtokorrelyatsiyani modellamaydi (qisqa ufqda ARIMA/ETS yutishi mumkin),
        sozlamasiz trend ekstrapolyatsiyasi ba'zan g'alati;
        "avtomatik" = backtestsiz ishonish degani EMAS
python
# faqat g'oya - bu muhitda prophet o'rnatilmagan
from prophet import Prophet

df = pd.DataFrame({"ds": y.index, "y": np.log(y.to_numpy())})
bayramlar = pd.DataFrame({"holiday": "ramazon_hayiti",
                          "ds": pd.to_datetime(["2024-04-10", "2025-03-30"]),
                          "lower_window": -2, "upper_window": 1})
m = Prophet(holidays=bayramlar, yearly_seasonality=True, weekly_seasonality=True)
m.fit(df)
kelajak = m.make_future_dataframe(periods=90)
prognoz = m.predict(kelajak)[["ds", "yhat", "yhat_lower", "yhat_upper"]]

Uning g'oyasini — Fourier mavsumlari va bayram belgilari bilan regressiya — 28.3-darsda ML modeliga belgi sifatida beramiz. Prophet ham boshqa modellar kabi seasonal naive ga qarshi backtestdan o'tishi shart.

2.12. Tuzoqlar

Asosiy tuzoqlar: AIC ni turli d, D yoki turli almashtirish (y va log y) orasida solishtirish; bitta test oynasi bo'yicha model tanlash; model intervaliga tekshirmasdan ishonish; exp(log bashorat) ni o'rtacha deb talqin qilish; Ljung-Box da model_df ni unutish va birinchi d + D*m qoldiqni tashlamaslik; SES yoki Holt ni mavsumli qatorga qo'llash; Holt ning chiziqli trendini uzoq ufqqa cho'zish; ARIMAX da kelajakda noma'lum o'zgaruvchini haqiqiy qiymati bilan backtest qilish; har oynada to'liq setka qidirish (vaqt byudjeti portlaydi); statsmodels ogohlantirishlarini butun dastur bo'yicha o'chirish; kunlik ma'lumotga m = 365 li SARIMA.


3. Tez ma'lumotnoma

python
import warnings
import numpy as np
from statsmodels.tsa.holtwinters import ExponentialSmoothing, SimpleExpSmoothing
from statsmodels.tsa.exponential_smoothing.ets import ETSModel
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.statespace.sarimax import SARIMAX
from statsmodels.stats.diagnostic import acorr_ljungbox

ly = np.log(y)                                       # y: freq="MS" li Series

with warnings.catch_warnings():
    warnings.simplefilter("ignore")                  # ConvergenceWarning va h.k.
    hw = ExponentialSmoothing(y, trend="add", damped_trend=True,
                              seasonal="mul", seasonal_periods=12).fit()
    ets = ETSModel(ly, error="add", trend="add", damped_trend=True,
                   seasonal="add", seasonal_periods=12).fit(disp=False)
    ar = ARIMA(x, order=(1, 0, 1), trend="c").fit()
    sar = SARIMAX(ly, exog=ramazon, order=(1, 1, 1),
                  seasonal_order=(0, 1, 1, 12)).fit(disp=False)

hw.forecast(12)
jad = ets.get_prediction(start=len(ly), end=len(ly) + 11).summary_frame(alpha=0.05)
f = sar.get_forecast(12, exog=ramazon_kelajak)       # exog kelajakda ma'lum!
f.predicted_mean, f.conf_int(alpha=0.05)

sar.aic, sar.bic                                     # faqat bir xil d, D ichida
acorr_ljungbox(sar.resid.iloc[13:], lags=[24], model_df=3)
sar.apply(ly_yangi, exog=ramazon_yangi)             # refit yo'q, holat yangilanadi
SARIMAX(...).fit(start_params=sar.params, disp=False)   # issiq start

qamrov = np.mean((haqiqiy >= past) & (haqiqiy <= yuqori))   # backtestda

Qaysi vaziyatda nima

Vaziyat Boshlang'ich model
Trend yo'q, mavsum yo'q, shovqinli SES / ETS(A,N,N)
Trend bor, mavsum yo'q Holt, damped trend
Oylik/choraklik, mavsumli, o'sadi log + ETS(A,Ad,A) yoki SARIMA "airline"
Harakatlanuvchi bayram, ma'lum tadbirlar ARIMAX (tashqi o'zgaruvchi)
Kunlik + yillik mavsum (m = 365) Fourier belgili ARIMAX yoki ML (28.3)
Juda ko'p qator (minglab mahsulot) ETS avtomatik yoki global ML model (28.3)
Har qanday holatda seasonal naive bilan juftlashgan backtest

Klassik modellar xulosasi

SES: l_t = alfa*y_t + (1-alfa)*l_{t-1}; Holt +trend; HW +mavsum; ETS - likelihood
ARIMA(p,d,q): AR - o'tmish qiymatlar, MA - o'tmish shoklar, d - farqlash
SARIMA (P,D,Q)_m; airline = (0,1,1)(0,1,1)_12
AIC - faqat bir xil d, D va almashtirish ichida; Ljung-Box qoldiqda
interval qamrovi - backtestda o'lchanadi; ARIMAX - exog kelajakda ma'lum bo'lsin

4. Batafsil misollar

Misollar real numpy/pandas/scipy/statsmodels bilan (Python 3.14). Har misol mustaqil ishlaydi. statsmodels ogohlantirishlari faqat fit chaqiruvlari atrofida warnings.catch_warnings() bilan o'raladi (28.1, 2.4-bo'limdagi izoh).

Misol 1 — Eksponensial silliqlash: SES noldan, Holt-Winters va ETS

python
"""Eksponensial silliqlash: SES noldan, Holt, damped trend, Holt-Winters, ETS."""

import warnings

import numpy as np
import pandas as pd
from scipy.optimize import minimize_scalar
from statsmodels.tsa.exponential_smoothing.ets import ETSModel
from statsmodels.tsa.holtwinters import ExponentialSmoothing, SimpleExpSmoothing

OY_MAVSUM = np.log([1.25, 1.18, 1.02, 0.90, 0.86, 0.95,
                    1.08, 1.06, 0.90, 0.88, 0.97, 1.15])


def ramazon_kunlari(sana):
    """Sintetik taqvim: har oyda nechta 'Ramazon kuni' bor (yiliga ~11 kun oldinga)."""
    kunlar = pd.date_range(sana[0], sana[-1] + pd.offsets.MonthEnd(0), freq="D")
    belgi = np.zeros(len(kunlar), dtype=int)
    for k in range(sana[0].year - 2011, sana[-1].year - 2011 + 1):
        bosh = pd.Timestamp(2011 + k, 8, 1) - pd.Timedelta(days=round(10.9 * k))
        belgi[(kunlar >= bosh) & (kunlar < bosh + pd.Timedelta(days=30))] = 1
    return pd.Series(belgi, index=kunlar).resample("MS").sum().to_numpy()


def elektr(seed=0, oylar=180):
    """Viloyatning oylik elektr iste'moli, GWh (sintetik)."""
    rng = np.random.default_rng(seed)
    sana = pd.date_range("2011-01-01", periods=oylar, freq="MS")
    t = np.arange(oylar)
    e = rng.normal(0, 0.02, oylar)
    shovqin = np.zeros(oylar)
    for i in range(1, oylar):
        shovqin[i] = 0.5 * shovqin[i - 1] + e[i]
    daraja = np.cumsum(rng.normal(0, 0.006, oylar))
    ly = (np.log(900) + 0.035 * t / 12 + daraja
          + (OY_MAVSUM - OY_MAVSUM.mean())[sana.month - 1]
          + 0.002 * ramazon_kunlari(sana) + shovqin)
    return pd.Series(np.exp(ly), index=sana, name="GWh")


def ses_noldan(y, alfa):
    """Bir qadamli prognozlar va SSE. l_0 = y_0."""
    daraja = y[0]
    prognoz = np.empty(len(y))
    for t, v in enumerate(y):
        prognoz[t] = daraja
        daraja = alfa * v + (1 - alfa) * daraja
    return prognoz, daraja, float(np.sum((y[1:] - prognoz[1:]) ** 2))


def main() -> None:
    rng = np.random.default_rng(1)
    print("=== 1. SES noldan va statsmodels bilan ===")
    talab = 40 + np.cumsum(rng.normal(0, 1.0, 120)) + rng.normal(0, 3.0, 120)
    q = pd.Series(talab, index=pd.date_range("2016-01-01", periods=120,
                                               freq="MS"))
    r = minimize_scalar(lambda a: ses_noldan(talab, a)[2],
                        bounds=(0.001, 0.999), method="bounded",
                        options={"xatol": 1e-6})
    _, oxirgi, sse = ses_noldan(talab, r.x)
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        sm = SimpleExpSmoothing(q, initialization_method="known",
                                initial_level=talab[0]).fit()
    print(f"  ehtiyot qism talabi, 120 oy: noldan alfa={r.x:.4f}, "
          f"prognoz {oxirgi:.3f}")
    print(f"  statsmodels:               alfa={sm.params['smoothing_level']:.4f}"
          f", prognoz {sm.forecast(1).iloc[0]:.3f}")
    for alfa in [0.2, 0.6]:
        w = alfa * (1 - alfa) ** np.arange(6)
        print(f"  alfa={alfa}: oxirgi 6 kuzatuv vaznlari "
              + " ".join(f"{v:.3f}" for v in w) + f"  (jami {w.sum():.3f})")

    print("\n=== 2. Oylik elektr: modellar va 24 oylik test ===")
    y = elektr()
    tr, te = y.iloc[:-24], y.iloc[-24:]
    ltr = np.log(tr)
    prognoz = {"seasonal naive": np.tile(tr.iloc[-12:].to_numpy(), 2)}
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        modellar = {
            "SES": ExponentialSmoothing(tr).fit(),
            "Holt (trend)": ExponentialSmoothing(tr, trend="add").fit(),
            "HW qo'shiluvchi": ExponentialSmoothing(
                tr, trend="add", seasonal="add", seasonal_periods=12).fit(),
            "HW ko'paytiruvchi": ExponentialSmoothing(
                tr, trend="add", seasonal="mul", seasonal_periods=12).fit(),
            "HW ko'p., damped": ExponentialSmoothing(
                tr, trend="add", damped_trend=True, seasonal="mul",
                seasonal_periods=12).fit(),
        }
        for nom, m in modellar.items():
            prognoz[nom] = m.forecast(24).to_numpy()
        ets = ETSModel(ltr, error="add", trend="add", damped_trend=True,
                       seasonal="add", seasonal_periods=12).fit(disp=False)
        prognoz["ETS(A,Ad,A) log"] = np.exp(ets.forecast(24).to_numpy())
    print(f"  {'model':<20} {'MAE, GWh':>9} {'1-yil':>7} {'2-yil':>7}")
    for nom, p in prognoz.items():
        x = np.abs(p - te.to_numpy())
        print(f"  {nom:<20} {x.mean():>9.2f} {x[:12].mean():>7.2f} "
              f"{x[12:].mean():>7.2f}")
    hw = modellar["HW ko'p., damped"].params
    print(f"  damped HW: alfa={hw['smoothing_level']:.3f}, "
          f"beta={hw['smoothing_trend']:.3f}, "
          f"gamma={hw['smoothing_seasonal']:.3f}, phi={hw['damping_trend']:.3f}")

    print("\n=== 3. ETS: AIC bilan tanlash (log qatorda, bir xil ma'lumot) ===")
    variantlar = [("A", None, False, "A"), ("A", "A", False, "A"),
                  ("A", "A", True, "A"), ("M", "A", True, "A")]
    natija = []
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        for xato, trend, damp, mavs in variantlar:
            m = ETSModel(ltr, error="add" if xato == "A" else "mul",
                         trend=None if trend is None else "add",
                         damped_trend=damp, seasonal="add",
                         seasonal_periods=12).fit(disp=False)
            nom = f"ETS({xato},{'N' if trend is None else trend}"
            nom += f"{'d' if damp else ''},{mavs})"
            p = np.exp(m.forecast(24).to_numpy())
            natija.append((m.aic, nom, np.mean(np.abs(p - te.to_numpy()))))
    eng = min(natija)[0]
    for aic, nom, mae in sorted(natija):
        print(f"  {nom:<14} AIC {aic:>9.2f}  dAIC {aic - eng:>6.2f}  "
              f"test MAE {mae:>6.2f}")

    print("\n=== 4. ETS(A,Ad,A) 95% bashorat intervali (24 oy) ===")
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        jad = ets.get_prediction(start=len(ltr), end=len(ltr) + 23).summary_frame(
            alpha=0.05)
    past, yuqori = np.exp(jad["pi_lower"].to_numpy()), np.exp(jad["pi_upper"].to_numpy())
    ichida = (te.to_numpy() >= past) & (te.to_numpy() <= yuqori)
    for h in [1, 6, 12, 24]:
        print(f"  h={h:>2}: [{past[h - 1]:7.1f}, {yuqori[h - 1]:7.1f}]  "
              f"kenglik {yuqori[h - 1] - past[h - 1]:6.1f}  "
              f"haqiqiy {te.iloc[h - 1]:7.1f}")
    print(f"  qamrov: {ichida.sum()}/24 = {ichida.mean():.3f} (nominal 0.95)")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. SES noldan va statsmodels bilan ===
  ehtiyot qism talabi, 120 oy: noldan alfa=0.1419, prognoz 32.512
  statsmodels:               alfa=0.1419, prognoz 32.512
  alfa=0.2: oxirgi 6 kuzatuv vaznlari 0.200 0.160 0.128 0.102 0.082 0.066  (jami 0.738)
  alfa=0.6: oxirgi 6 kuzatuv vaznlari 0.600 0.240 0.096 0.038 0.015 0.006  (jami 0.996)

=== 2. Oylik elektr: modellar va 24 oylik test ===
  model                 MAE, GWh   1-yil   2-yil
  seasonal naive           77.50   38.33  116.66
  SES                     156.22  159.23  153.21
  Holt (trend)            160.66  166.20  155.12
  HW qo'shiluvchi          49.14   30.84   67.44
  HW ko'paytiruvchi        51.59   26.65   76.54
  HW ko'p., damped         65.12   31.10   99.14
  ETS(A,Ad,A) log          73.88   33.60  114.16
  damped HW: alfa=0.620, beta=0.000, gamma=0.000, phi=0.992

=== 3. ETS: AIC bilan tanlash (log qatorda, bir xil ma'lumot) ===
  ETS(A,N,A)     AIC   -654.62  dAIC   0.00  test MAE  76.77
  ETS(A,A,A)     AIC   -653.03  dAIC   1.59  test MAE  48.24
  ETS(A,Ad,A)    AIC   -650.43  dAIC   4.19  test MAE  73.88
  ETS(M,Ad,A)    AIC   -649.77  dAIC   4.85  test MAE  73.88

=== 4. ETS(A,Ad,A) 95% bashorat intervali (24 oy) ===
  h= 1: [ 1439.0,  1597.4]  kenglik  158.4  haqiqiy  1546.6
  h= 6: [ 1079.8,  1283.7]  kenglik  203.9  haqiqiy  1169.7
  h=12: [ 1247.1,  1569.2]  kenglik  322.0  haqiqiy  1426.6
  h=24: [ 1197.7,  1639.3]  kenglik  441.6  haqiqiy  1524.2
  qamrov: 23/24 = 0.958 (nominal 0.95)

Natija tahlili.

1-bo'lim — SES noldan. 120 oylik ehtiyot qism talabida (sekin sayr qiluvchi daraja + shovqin) SSE ni minimallashtirgan alfa noldan ham, statsmodels da ham 0.1419, keyingi oy prognozi ikkalasida 32.512. Formulani to'g'ri tushunganimiz tasdiqlandi. Vaznlarga qarang: alfa = 0.2 da oxirgi 6 kuzatuv jami vaznning faqat 0.738 ini oladi — model uzoq o'tmishni ham eslaydi; alfa = 0.6 da 0.996 — deyarli faqat oxirgi yarim yil.

2-bo'lim — oylik elektr iste'moli, oxirgi 24 oy test. SES (156.22) va Holt (160.66) seasonal naive dan (77.50) ikki barobar yomon: ular mavsumni bilmaydi va qishki cho'qqi bilan bahorgi pastlik o'rtasidagi tekis chiziqni chizadi. Mavsumli modellar ancha yaxshi: qo'shiluvchi HW 49.14, ko'paytiruvchi HW 51.59. Seasonal naive ning zaif joyi 2-yilda ko'rinadi: 1-yilda 38.33, 2-yilda 116.66 — u o'tgan yilni takrorlaydi, shuning uchun ikkinchi yilda trenddan ikki yil orqada qoladi. So'nuvchi trendli HW (65.12) va log fazodagi ETS(A,Ad,A) (73.88) ham 2-yilda kuchsizlandi: bu qatorda trend haqiqatan davom etdi, "so'ndirish" esa uni tekislab qo'ydi. Damped HW parametrlari ham ibratli: beta = 0.000 va gamma = 0.000 — trend va mavsum profili o'quv davomida deyarli o'zgarmas deb topildi, phi = 0.992 — sekin so'nish.

3-bo'lim — ETS variantlarini AIC bilan tanlash (hammasi log qatorda, bir xil ma'lumot — AIC solishtirish qonuniy). Eng past AIC — trendsiz ETS(A,N,A) (-654.62), trendli ETS(A,A,A) dAIC = 1.59 bilan deyarli teng. Lekin test MAE boshqacha: ETS(A,N,A) 76.77, ETS(A,A,A) 48.24. AIC o'quv davridagi bir qadamli moslik va parametrlar sonini baholaydi, 24 oylik ekstrapolyatsiyani emas; bitta test oynasi esa o'zi shovqinli. Ikkala savolga javob — ko'p oynali backtest (4-misol). ETS(M,Ad,A) va ETS(A,Ad,A) test MAE si bir xil (73.88): xato turi nuqtali bashoratni o'zgartirmaydi, faqat likelihood va intervallarni.

4-bo'lim — interval ufq bilan kengayadi: 1 oy oldinga kengligi 158.4 GWh, 24 oy oldinga 441.6. 24 nuqtadan 23 tasi intervalga tushdi (0.958). Bu yaxshi ko'rinadi, lekin bitta oynaning 24 nuqtasi bir-biriga bog'liq — qamrov haqida xulosa qilish uchun kam. Uni 4-misolda 10 oynada o'lchaymiz.

Misol 2 — ARIMA: imzolar, parametrlar, AIC setkasi va d tuzog'i

python
"""ARIMA: imzolar, parametrlarni tiklash, AIC setkasi, Ljung-Box va "AIC turli d da" tuzog'i."""

import warnings

import numpy as np
from statsmodels.stats.diagnostic import acorr_ljungbox
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.stattools import acf, adfuller, pacf


def simulyatsiya(rng, n, ar=(), ma=(), kuyish=200):
    """ARMA jarayoni: x_t = sum ar_i x_{t-i} + e_t + sum ma_j e_{t-j}."""
    e = rng.normal(0, 1, n + kuyish)
    x = np.zeros(n + kuyish)
    for t in range(n + kuyish):
        x[t] = e[t]
        for i, a in enumerate(ar, 1):
            if t - i >= 0:
                x[t] += a * x[t - i]
        for j, b in enumerate(ma, 1):
            if t - j >= 0:
                x[t] += b * e[t - j]
    return x[kuyish:]


def moslash(x, order, trend="n"):
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")          # optimizator ogohlantirishlari
        return ARIMA(x, order=order, trend=trend).fit()


def main() -> None:
    rng = np.random.default_rng(0)
    n = 400
    jarayonlar = {
        "AR(2) 0.6, 0.25": dict(ar=(0.6, 0.25)),
        "MA(1) 0.6": dict(ma=(0.6,)),
        "ARMA(1,1) 0.7, 0.4": dict(ar=(0.7,), ma=(0.4,)),
    }
    qatorlar = {k: simulyatsiya(rng, n, **v) for k, v in jarayonlar.items()}
    chegara = 1.96 / np.sqrt(n)
    print("=== 1. ACF va PACF imzolari (n=400, chegara +-0.098) ===")
    for nom, x in qatorlar.items():
        a, p = acf(x, nlags=5), pacf(x, nlags=5, method="ywm")
        yulduz = lambda v: "*" if abs(v) > chegara else " "
        print(f"  {nom}")
        print("    ACF  " + " ".join(f"{v:+.2f}{yulduz(v)}" for v in a[1:]))
        print("    PACF " + " ".join(f"{v:+.2f}{yulduz(v)}" for v in p[1:]))

    print("\n=== 2. Parametrlarni tiklash (baho +- SE, haqiqiy) ===")
    for (nom, x), order, haq in zip(qatorlar.items(),
                                    [(2, 0, 0), (0, 0, 1), (1, 0, 1)],
                                    [(0.6, 0.25), (0.6,), (0.7, 0.4)]):
        r = moslash(x, order)
        nomlar = [k for k in r.param_names if k != "sigma2"]
        qism = ", ".join(f"{k} {r.params[i]:+.3f}+-{r.bse[i]:.3f} ({h})"
                         for i, (k, h) in enumerate(zip(nomlar, haq)))
        print(f"  {nom:<20} {qism}")

    print("\n=== 3. AIC/BIC setkasi (ARMA(1,1) qatori, d=0) ===")
    x = qatorlar["ARMA(1,1) 0.7, 0.4"]
    setka = []
    for p in range(3):
        for q in range(3):
            r = moslash(x, (p, 0, q))
            setka.append((r.aic, r.bic, p, q))
    eng_aic = min(setka)[0]
    eng_bic = min(s[1] for s in setka)
    print(f"  {'(p,q)':<7} {'AIC':>8} {'dAIC':>6} {'BIC':>8} {'dBIC':>6}")
    for aic, bic, p, q in sorted(setka):
        print(f"  ({p},{q})   {aic:>8.1f} {aic - eng_aic:>6.1f} {bic:>8.1f} "
              f"{bic - eng_bic:>6.1f}")
    print(f"  AIC tanlovi: ({sorted(setka)[0][2]},{sorted(setka)[0][3]}), "
          f"BIC tanlovi: {min(setka, key=lambda s: s[1])[2:]}")

    print("\n=== 4. Qoldiqlar diagnostikasi: Ljung-Box (lag 10) ===")
    x = qatorlar["AR(2) 0.6, 0.25"]
    for order in [(1, 0, 0), (2, 0, 0)]:
        r = moslash(x, order)
        lb = acorr_ljungbox(r.resid, lags=[10], model_df=order[0] + order[2])
        p = float(lb["lb_pvalue"].iloc[0])
        a1 = acf(r.resid, nlags=2)
        print(f"  AR({order[0]}) qoldiq: ACF(1) {a1[1]:+.3f}, ACF(2) "
              f"{a1[2]:+.3f}, Ljung-Box p = {p:.4f} -> "
              f"{'qoldiqda tuzilish QOLGAN' if p < 0.05 else 'oq shovqinga o_xshaydi'}")

    print("\n=== 5. Tuzoq: AIC ni turli d orasida solishtirish ===")
    z = 50 + simulyatsiya(np.random.default_rng(7), 150, ar=(0.9,))
    print("  bitta STATSIONAR AR(1) phi=0.9 qatori (n=150), ikki birlikda:")
    print(f"  {'birlik':<8} {'AIC (1,0,0)':>12} {'AIC (2,0,0)':>12} "
          f"{'AIC (0,1,0)':>12}  AIC tanlovi")
    for birlik, k in [("GWh", 1.0), ("MWh", 1000.0)]:
        r10 = moslash(k * z, (1, 0, 0), trend="c")
        r20 = moslash(k * z, (2, 0, 0), trend="c")
        r01 = moslash(k * z, (0, 1, 0))
        aic = {"(1,0,0)": r10.aic, "(2,0,0)": r20.aic, "(0,1,0)": r01.aic}
        print(f"  {birlik:<8} {r10.aic:>12.1f} {r20.aic:>12.1f} "
              f"{r01.aic:>12.1f}  {min(aic, key=aic.get)}")
        if birlik == "GWh":
            farq_ichki, farq_d = r20.aic - r10.aic, r01.aic - r10.aic
        else:
            f1, f2 = r20.aic - r10.aic, r01.aic - r10.aic
            print(f"  d=0 ichida farq (2,0,0)-(1,0,0): {farq_ichki:+.2f} -> "
                  f"{f1:+.2f} (siljish {f1 - farq_ichki:+.2f})")
            print(f"  d lar orasida farq (0,1,0)-(1,0,0): {farq_d:+.2f} -> "
                  f"{f2:+.2f} (siljish {f2 - farq_d:+.2f}; "
                  f"2*ln(1000) = {2 * np.log(1000):.2f})")
    print(f"  sababi: log-likelihood dan chiqarilgan boshlang'ich qadamlar "
          f"d=0 -> {r10.loglikelihood_burn}, d=1 -> {r01.loglikelihood_burn}")

    print("\n=== 6. 40 ta shunday qator: d tanlovi va 12 qadamli xato ===")
    tanlov = {"AIC, GWh": [], "AIC, MWh": [], "ADF (p>=0.05 -> d=1)": []}
    xato = {"d=0 (haqiqiy)": [], "d=1": []}
    for s in range(40):
        z = 50 + simulyatsiya(np.random.default_rng(100 + s), 162, ar=(0.9,))
        tr, te = z[:150], z[150:]
        m0, m1 = moslash(tr, (1, 0, 0), trend="c"), moslash(tr, (0, 1, 0))
        xato["d=0 (haqiqiy)"].append(np.mean(np.abs(m0.forecast(12) - te)))
        xato["d=1"].append(np.mean(np.abs(m1.forecast(12) - te)))
        siljish = 2 * np.log(1000)            # MWh da (0,1,0)-(1,0,0) farqi shuncha kamayadi
        tanlov["AIC, GWh"].append(int(m1.aic < m0.aic))
        tanlov["AIC, MWh"].append(int(m1.aic - siljish < m0.aic))
        tanlov["ADF (p>=0.05 -> d=1)"].append(int(adfuller(
            tr, regression="c", autolag="AIC", result_object=False)[1] >= 0.05))
    for nom, v in tanlov.items():
        print(f"  {nom:<22} d=1 ni tanlagan ulush: {np.mean(v):.3f}")
    for nom, v in xato.items():
        print(f"  {nom:<22} MAE (12 qadam): {np.mean(v):.3f}")

if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. ACF va PACF imzolari (n=400, chegara +-0.098) ===
  AR(2) 0.6, 0.25
    ACF  +0.74* +0.66* +0.56* +0.51* +0.45*
    PACF +0.74* +0.25* +0.02  +0.05  +0.03
  MA(1) 0.6
    ACF  +0.49* +0.12* +0.15* +0.06  -0.01
    PACF +0.49* -0.15* +0.21* -0.13* +0.04
  ARMA(1,1) 0.7, 0.4
    ACF  +0.78* +0.49* +0.28* +0.14* +0.08
    PACF +0.78* -0.33* +0.08  -0.04  +0.04

=== 2. Parametrlarni tiklash (baho +- SE, haqiqiy) ===
  AR(2) 0.6, 0.25      ar.L1 +0.559+-0.053 0.6-bob, ar.L2 +0.264+-0.054 0.25-bob
  MA(1) 0.6            ma.L1 +0.630+-0.042 0.6-bob
  ARMA(1,1) 0.7, 0.4   ar.L1 +0.647+-0.048 0.7-bob, ma.L1 +0.414+-0.054 0.4-bob

=== 3. AIC/BIC setkasi (ARMA(1,1) qatori, d=0) ===
  (p,q)        AIC   dAIC      BIC   dBIC
  (1,1)     1153.2    0.0   1165.2    0.0
  (2,1)     1154.2    1.0   1170.2    5.0
  (1,2)     1154.3    1.1   1170.3    5.1
  (2,0)     1155.7    2.4   1167.6    2.4
  (2,2)     1156.1    2.9   1176.1   10.9
  (0,2)     1187.8   34.5   1199.7   34.5
  (1,0)     1198.6   45.3   1206.5   41.3
  (0,1)     1280.7  127.4   1288.7  123.4
  (0,0)     1580.6  427.4   1584.6  419.4
  AIC tanlovi: (1,1), BIC tanlovi: (1, 1)

=== 4. Qoldiqlar diagnostikasi: Ljung-Box (lag 10) ===
  AR(1) qoldiq: ACF(1) -0.199, ACF(2) +0.131, Ljung-Box p = 0.0000 -> qoldiqda tuzilish QOLGAN
  AR(2) qoldiq: ACF(1) -0.009, ACF(2) -0.035, Ljung-Box p = 0.2141 -> oq shovqinga o_xshaydi

=== 5. Tuzoq: AIC ni turli d orasida solishtirish ===
  bitta STATSIONAR AR(1) phi=0.9 qatori (n=150), ikki birlikda:
  birlik    AIC (1,0,0)  AIC (2,0,0)  AIC (0,1,0)  AIC tanlovi
  GWh             429.1        429.9        427.5  (0,1,0)
  MWh            2501.5       2502.3       2486.0  (0,1,0)
  d=0 ichida farq (2,0,0)-(1,0,0): +0.74 -> +0.72 (siljish -0.03)
  d lar orasida farq (0,1,0)-(1,0,0): -1.62 -> -15.53 (siljish -13.92; 2*ln(1000) = 13.82)
  sababi: log-likelihood dan chiqarilgan boshlang'ich qadamlar d=0 -> 0, d=1 -> 1

=== 6. 40 ta shunday qator: d tanlovi va 12 qadamli xato ===
  AIC, GWh               d=1 ni tanlagan ulush: 0.500
  AIC, MWh               d=1 ni tanlagan ulush: 1.000
  ADF (p>=0.05 -> d=1)   d=1 ni tanlagan ulush: 0.550
  d=0 (haqiqiy)          MAE (12 qadam): 1.574
  d=1                    MAE (12 qadam): 1.814

Natija tahlili.

1-bo'lim — uch jarayonning imzolari. AR(2): ACF sekin so'nadi (0.74 → 0.45), PACF ikki lagdan keyin uziladi (0.74, 0.25, keyin chegara ichida) — "p = 2". MA(1): ACF birinchi lagda 0.49 (nazariy 0.6 / 1.36 = 0.44), keyingi laglarda kichik bo'lsa ham 2 va 3-lag chegaradan arang chiqdi (0.12, 0.15) — tanlanma shovqini; PACF ishorasini almashtirib so'nadi. ARMA(1,1): ikkalasi ham so'nadi — bunday holatda ACF/PACF faqat taxmin beradi, aniq tartibni AIC hal qiladi.

2-bo'lim — parametrlar tiklandi: barcha baholar haqiqiy qiymatdan 1 SE atrofida (0.559 +- 0.053 va 0.6, 0.630 +- 0.042 va 0.6). 400 nuqtada ham SE ~0.05 — AR koeffitsientining ikkinchi xonasi allaqachon noaniq.

3-bo'lim — AIC setkasi haqiqiy (1,1) ni tanladi; BIC ham. Lekin (2,1) va (1,2) dAIC ~ 1 bilan deyarli teng — AIC nuqtai nazaridan ular "bir xil yaxshi", soddasini olamiz. BIC ortiqcha parametrni qattiqroq jazolaydi: (2,1) uchun dBIC = 5.0. Pastki qatorlarda farq keskin: MA qismisiz (1,0) dAIC = 45.3, oq shovqin (0,0) 427.4.

4-bo'lim — Ljung-Box. AR(2) qatoriga AR(1) moslasak, qoldiqda ACF(1) -0.199, ACF(2) +0.131 qoladi, p = 0.0000 — model yetishmaydi. To'g'ri AR(2) da qoldiq ACF lari nolga yaqin, p = 0.2141 — tuzilish topilmadi.

5-bo'lim — asosiy tuzoq. Bitta statsionar AR(1) qatori (phi = 0.9), bir xil modellar, faqat birlik boshqa: GWh va MWh (×1000). d = 0 ichidagi farq (2,0,0)-(1,0,0) birlikka bog'liq emas (+0.74 → +0.72, siljish -0.03 — faqat optimizator aniqligi). d lar orasidagi farq esa -1.62 dan -15.53 ga siljidi (-13.92, nazariy 2*ln(1000) = 13.82): d = 1 modelning likelihood i bitta kuzatuvga kam hisoblangan (loglikelihood_burn: 0 va 1), shuning uchun birlik o'zgarganda ikki AIC turlicha siljiydi. Ya'ni "qaysi d yaxshi?" degan savolga AIC ning javobi o'lchov birligiga bog'liq — bu javob ma'nosiz ekanining eng oddiy isboti. (Bu qatorda GWh da ham AIC d = 1 ni tanladi.)

6-bo'lim — 40 ta shunday qatorda: AIC GWh da 50% hollarda noto'g'ri d = 1 ni tanladi, MWh da — 100% hollarda. Shu ma'lumot, shu modellar, faqat birlik. ADF ham mukammal emas: kichik namunada (n = 150, phi = 0.9) quvvati past va 55% hollarda "birlik ildiz bor" dedi. Noto'g'ri d ning narxi: 12 qadamli MAE 1.574 dan 1.814 ga oshadi — statsionar qator o'rtachaga qaytadi, tasodifiy yurish modeli esa buni bilmaydi. Amaliy xulosa: d ni domen bilimi (bu qator o'rtachaga qaytadimi?), testlar va — shubhada — ko'p oynali backtest bilan tanlang; AIC ni faqat tanlangan d ichida ishlating.

Misol 3 — SARIMA va ARIMAX: Ramazon kunlari tashqi o'zgaruvchi sifatida

python
"""SARIMA: farqlangan qator ACF, kichik AIC setkasi, Ljung-Box va ARIMAX (Ramazon kunlari)."""

import warnings

import numpy as np
import pandas as pd
from statsmodels.stats.diagnostic import acorr_ljungbox
from statsmodels.tsa.statespace.sarimax import SARIMAX
from statsmodels.tsa.stattools import acf

OY_MAVSUM = np.log([1.25, 1.18, 1.02, 0.90, 0.86, 0.95,
                    1.08, 1.06, 0.90, 0.88, 0.97, 1.15])


def ramazon_kunlari(sana):
    """Sintetik taqvim: har oyda nechta 'Ramazon kuni' bor (yiliga ~11 kun oldinga)."""
    kunlar = pd.date_range(sana[0], sana[-1] + pd.offsets.MonthEnd(0), freq="D")
    belgi = np.zeros(len(kunlar), dtype=int)
    for k in range(sana[0].year - 2011, sana[-1].year - 2011 + 1):
        bosh = pd.Timestamp(2011 + k, 8, 1) - pd.Timedelta(days=round(10.9 * k))
        belgi[(kunlar >= bosh) & (kunlar < bosh + pd.Timedelta(days=30))] = 1
    return pd.Series(belgi, index=kunlar).resample("MS").sum().to_numpy()


def elektr(seed=0, oylar=180):
    """Viloyatning oylik elektr iste'moli, GWh (sintetik)."""
    rng = np.random.default_rng(seed)
    sana = pd.date_range("2011-01-01", periods=oylar, freq="MS")
    t = np.arange(oylar)
    e = rng.normal(0, 0.02, oylar)
    shovqin = np.zeros(oylar)
    for i in range(1, oylar):
        shovqin[i] = 0.5 * shovqin[i - 1] + e[i]
    daraja = np.cumsum(rng.normal(0, 0.006, oylar))
    ly = (np.log(900) + 0.035 * t / 12 + daraja
          + (OY_MAVSUM - OY_MAVSUM.mean())[sana.month - 1]
          + 0.002 * ramazon_kunlari(sana) + shovqin)
    return pd.Series(np.exp(ly), index=sana, name="GWh")


def sarima(y, order, exog=None):
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")          # ConvergenceWarning va h.k.
        return SARIMAX(y, exog=exog, order=order,
                       seasonal_order=(0, 1, 1, 12)).fit(disp=False)


def main() -> None:
    y = elektr()
    ly = np.log(y)
    ram = pd.Series(ramazon_kunlari(y.index), index=y.index, name="ramazon")
    n_te = 24
    ltr, te = ly.iloc[:-n_te], y.iloc[-n_te:]

    print("=== 1. Farqlangan qatorning ACF i: (1-B)(1-B^12) log y ===")
    dd = ltr.diff(12).diff().dropna()
    a = acf(dd, nlags=24)
    chegara = 1.96 / np.sqrt(len(dd))
    print(f"  n={len(dd)}, chegara +-{chegara:.3f}")
    print("  " + "  ".join(f"lag{k}:{a[k]:+.2f}{'*' if abs(a[k]) > chegara else ''}"
                           for k in [1, 2, 3, 11, 12, 13, 24]))
    print("  1-lag va 12-lagda manfiy cho'qqi -> MA(1) va mavsumiy MA(1)")

    print("\n=== 2. SARIMA(p,1,q)(0,1,1)_12: kichik AIC setkasi (d, D bir xil) ===")
    setka = []
    for p in [0, 1]:
        for q in [0, 1]:
            r = sarima(ltr, (p, 1, q))
            prog = np.exp(r.forecast(n_te).to_numpy())
            setka.append((r.aic, p, q, float(np.mean(np.abs(prog - te.to_numpy())))))
    eng = min(setka)[0]
    for aic, p, q, mae in sorted(setka):
        print(f"  ({p},1,{q})(0,1,1)12  AIC {aic:>9.2f}  dAIC {aic - eng:>5.2f}"
              f"  test MAE {mae:>6.2f}")
    _, p_eng, q_eng, _ = min(setka)
    order = (p_eng, 1, q_eng)

    print(f"\n=== 3. Qoldiqlar: SARIMA{order}(0,1,1)12 ===")
    r = sarima(ltr, order)
    qoldiq = r.resid.iloc[13:]                     # farqlash boshlanishi tashlanadi
    df = p_eng + q_eng + 1
    for lag in [12, 24]:
        lb = acorr_ljungbox(qoldiq, lags=[lag], model_df=df)
        print(f"  Ljung-Box lag {lag}: p = {float(lb['lb_pvalue'].iloc[0]):.4f}")
    qoldiq_ram = pd.Series(qoldiq.to_numpy(), index=qoldiq.index)
    ram_oy = ram.loc[qoldiq.index] >= 15
    print(f"  qoldiq o'rtachasi: Ramazonli oylar {qoldiq_ram[ram_oy].mean():+.4f},"
          f" boshqalar {qoldiq_ram[~ram_oy].mean():+.4f}")

    print("\n=== 4. ARIMAX: tashqi o'zgaruvchi - oydagi Ramazon kunlari ===")
    rx = sarima(ltr, order, exog=ram.iloc[:-n_te])
    b, se = rx.params["ramazon"], rx.bse["ramazon"]
    print(f"  koeffitsient: {b:.5f} +- {se:.5f} (haqiqiy 0.00200) -> "
          f"30 kunlik oy {100 * (np.exp(30 * b) - 1):.1f}% ko'proq")
    p_s = np.exp(r.forecast(n_te).to_numpy())
    p_x = np.exp(rx.forecast(n_te, exog=ram.iloc[-n_te:]).to_numpy())
    oy_ram = ram.iloc[-n_te:].to_numpy() >= 15
    print(f"  AIC: SARIMA {r.aic:.2f}, ARIMAX {rx.aic:.2f} (bir xil d, D, "
          f"bir xil ma'lumot)")
    lb = acorr_ljungbox(rx.resid.iloc[13:], lags=[24], model_df=df + 1)
    print(f"  ARIMAX Ljung-Box lag 24: p = {float(lb['lb_pvalue'].iloc[0]):.4f}")
    print(f"  {'model':<8} {'test MAE':>9} {'Ramazonli oylar':>16} {'boshqa':>7}")
    for nom, p in [("SARIMA", p_s), ("ARIMAX", p_x)]:
        x = np.abs(p - te.to_numpy())
        print(f"  {nom:<8} {x.mean():>9.2f} {x[oy_ram].mean():>16.2f} "
              f"{x[~oy_ram].mean():>7.2f}")
    print(f"  test davrida Ramazonli oylar: {int(oy_ram.sum())} ta; kelajak "
          f"exog qiymati oldindan ma'lum (taqvim)")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Farqlangan qatorning ACF i: (1-B)(1-B^12) log y ===
  n=143, chegara +-0.164
  lag1:-0.20*  lag2:-0.05  lag3:-0.18*  lag11:-0.05  lag12:-0.39*  lag13:-0.04  lag24:+0.13
  1-lag va 12-lagda manfiy cho'qqi -> MA(1) va mavsumiy MA(1)

=== 2. SARIMA(p,1,q)(0,1,1)_12: kichik AIC setkasi (d, D bir xil) ===
  (1,1,1)(0,1,1)12  AIC   -591.62  dAIC  0.00  test MAE  45.03
  (0,1,1)(0,1,1)12  AIC   -588.52  dAIC  3.10  test MAE  55.95
  (1,1,0)(0,1,1)12  AIC   -584.19  dAIC  7.43  test MAE  60.54
  (0,1,0)(0,1,1)12  AIC   -571.70  dAIC 19.92  test MAE  71.00

=== 3. Qoldiqlar: SARIMA(1, 1, 1)(0,1,1)12 ===
  Ljung-Box lag 12: p = 0.6543
  Ljung-Box lag 24: p = 0.0490
  qoldiq o'rtachasi: Ramazonli oylar +0.0157, boshqalar -0.0013

=== 4. ARIMAX: tashqi o'zgaruvchi - oydagi Ramazon kunlari ===
  koeffitsient: 0.00209 +- 0.00026 (haqiqiy 0.00200) -> 30 kunlik oy 6.5% ko'proq
  AIC: SARIMA -591.62, ARIMAX -650.63 (bir xil d, D, bir xil ma'lumot)
  ARIMAX Ljung-Box lag 24: p = 0.2439
  model     test MAE  Ramazonli oylar  boshqa
  SARIMA       45.03           103.43   39.73
  ARIMAX       36.77            37.47   36.70
  test davrida Ramazonli oylar: 2 ta; kelajak exog qiymati oldindan ma'lum (taqvim)

Natija tahlili.

1-bo'lim — (1 - B)(1 - B^12) log y ning ACF i: 1-lagda -0.20 va 12-lagda -0.39 — manfiy cho'qqilar, farqlangan qatorlarga xos: "airline" modeli (q = 1, Q = 1) tabiiy boshlang'ich nuqta. 3-lagda ham -0.18 bor — AR qismi foydali bo'lishi mumkin.

2-bo'lim — kichik setka, d = 1, D = 1 hammasi uchun bir xil, shuning uchun AIC lar solishtiriladi. Eng yaxshi — SARIMA(1,1,1)(0,1,1)12 (AIC -591.62); airline (0,1,1) dAIC = 3.10 bilan orqada. Bu safar test MAE ham AIC bilan bir xil tartibda (45.03 va 55.95), lekin bu kafolat emas (1-misoldagi ETS ni eslang).

3-bo'lim — qoldiqlar. Ljung-Box 12-lagda p = 0.6543 — yaxshi, lekin 24-lagda p = 0.0490 — chegarada. Sabab qoldiqlarni guruhlaganda ko'rinadi: Ramazonli oylarda (kamida 15 kun) qoldiq o'rtachasi +0.0157, boshqa oylarda -0.0013. Log fazoda +0.0157 — model Ramazon oylarida iste'molni muntazam ~1.6% kam bashorat qiladi. Oylik mavsum bu effektni ushlay olmaydi: Ramazon har yili boshqa kunlarga tushadi.

4-bo'lim — ARIMAX. "Oydagi Ramazon kunlari soni" tashqi o'zgaruvchi sifatida qo'shildi. Koeffitsient 0.00209 +- 0.00026 — sintetik ma'lumotga kiritilgan haqiqiy 0.00200 ga juda yaqin; 30 kunlik Ramazon oyi ~`6.5%ko'proq iste'mol. AIC-591.62dan-650.63ga tushdi (bir xild, D, bir xil ma'lumot — solishtirish qonuniy), 24-lagdagi Ljung-Box p = 0.2439— qoldiqdagi tuzilish yo'qoldi. Test davrida faqat 2 ta Ramazonli oy bor, lekin farq aynan ularda: SARIMA xatosi103.43, ARIMAX 37.47. Qolgan oylarda ikkalasi yaqin (39.73va36.70), umumiy MAE 45.03→36.77. Bu taqqoslash bitta oynada va 2 ta muhim oyda — 4-misolda uni ko'p oynada tekshiramiz. Muhim shart bajarilgan: Ramazon taqvimi oldindan ma'lum, ya'ni kelajakdagi exog` ni biz haqiqatan bilamiz.

Misol 4 — Rolling-origin backtest: juftlashgan taqqoslash va interval qamrovi

python
"""Rolling-origin backtest: seasonal naive, ETS, SARIMA, ARIMAX; juftlashgan SE va interval qamrovi."""

import warnings

import numpy as np
import pandas as pd
from statsmodels.tsa.exponential_smoothing.ets import ETSModel
from statsmodels.tsa.statespace.sarimax import SARIMAX

OY_MAVSUM = np.log([1.25, 1.18, 1.02, 0.90, 0.86, 0.95,
                    1.08, 1.06, 0.90, 0.88, 0.97, 1.15])


def ramazon_kunlari(sana):
    """Sintetik taqvim: har oyda nechta 'Ramazon kuni' bor (yiliga ~11 kun oldinga)."""
    kunlar = pd.date_range(sana[0], sana[-1] + pd.offsets.MonthEnd(0), freq="D")
    belgi = np.zeros(len(kunlar), dtype=int)
    for k in range(sana[0].year - 2011, sana[-1].year - 2011 + 1):
        bosh = pd.Timestamp(2011 + k, 8, 1) - pd.Timedelta(days=round(10.9 * k))
        belgi[(kunlar >= bosh) & (kunlar < bosh + pd.Timedelta(days=30))] = 1
    return pd.Series(belgi, index=kunlar).resample("MS").sum().to_numpy()


def elektr(seed=0, oylar=180):
    """Viloyatning oylik elektr iste'moli, GWh (sintetik)."""
    rng = np.random.default_rng(seed)
    sana = pd.date_range("2011-01-01", periods=oylar, freq="MS")
    t = np.arange(oylar)
    e = rng.normal(0, 0.02, oylar)
    shovqin = np.zeros(oylar)
    for i in range(1, oylar):
        shovqin[i] = 0.5 * shovqin[i - 1] + e[i]
    daraja = np.cumsum(rng.normal(0, 0.006, oylar))
    ly = (np.log(900) + 0.035 * t / 12 + daraja
          + (OY_MAVSUM - OY_MAVSUM.mean())[sana.month - 1]
          + 0.002 * ramazon_kunlari(sana) + shovqin)
    return pd.Series(np.exp(ly), index=sana, name="GWh")


class SarimaBashorat:
    """SARIMA(1,1,1)(0,1,1)12. Parametrlar har `har` oynada qayta baholanadi
    (oldingi parametrlardan boshlab), oraliqda faqat holat yangilanadi."""

    def __init__(self, exog=None, har=2):
        self.exog, self.har, self.natija, self.iter = exog, har, None, []

    def __call__(self, i, ly, h):
        ex = None if self.exog is None else self.exog.iloc[:len(ly)]
        ex_kel = None if self.exog is None else self.exog.iloc[len(ly):len(ly) + h]
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            if self.natija is None or i % self.har == 0:
                m = SARIMAX(ly, exog=ex, order=(1, 1, 1),
                            seasonal_order=(0, 1, 1, 12))
                bosh = None if self.natija is None else self.natija.params
                self.natija = m.fit(start_params=bosh, disp=False)
                self.iter.append(self.natija.mle_retvals["iterations"])
                r = self.natija
            else:
                r = self.natija.apply(ly, exog=ex)   # refit yo'q
            f = r.get_forecast(h, exog=ex_kel)
        ci = f.conf_int(alpha=0.05).to_numpy()
        return f.predicted_mean.to_numpy(), ci[:, 0], ci[:, 1]


def snaive(i, ly, h):
    x = ly.to_numpy()
    f = np.array([x[len(x) - 12 + (k % 12)] for k in range(h)])
    s = np.std(x[12:] - x[:-12], ddof=1)          # mavsumiy farqlar std
    return f, f - 1.96 * s, f + 1.96 * s


def ets(i, ly, h):
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        m = ETSModel(ly, error="add", trend="add", damped_trend=True,
                     seasonal="add", seasonal_periods=12).fit(disp=False)
        jad = m.get_prediction(start=len(ly), end=len(ly) + h - 1).summary_frame(
            alpha=0.05)
    return jad["mean"].to_numpy(), jad["pi_lower"].to_numpy(), jad["pi_upper"].to_numpy()


def main() -> None:
    y = elektr()
    ly = np.log(y)
    ram = pd.Series(ramazon_kunlari(y.index), index=y.index)
    h, n_oyna = 6, 10
    boshlar = [len(y) - h * (i + 1) for i in range(n_oyna)][::-1]
    sarima, arimax = SarimaBashorat(), SarimaBashorat(exog=ram)
    usullar = {"seasonal naive": snaive, "ETS(A,Ad,A)": ets,
               "SARIMA": sarima, "ARIMAX (Ramazon)": arimax}
    mae = {k: [] for k in usullar}
    ichida = {k: [] for k in usullar}
    kenglik = {k: [] for k in usullar}
    for i, o in enumerate(boshlar):
        haq = y.iloc[o:o + h].to_numpy()
        for nom, fn in usullar.items():
            f, past, yuq = fn(i, ly.iloc[:o], h)
            mae[nom].append(np.mean(np.abs(np.exp(f) - haq)))
            ichida[nom].extend((np.log(haq) >= past) & (np.log(haq) <= yuq))
            kenglik[nom].append(np.mean(np.exp(yuq) - np.exp(past)))
    mae = {k: np.array(v) for k, v in mae.items()}

    print("=== 1. Backtest dizayni ===")
    print(f"  {n_oyna} oyna, ufq {h} oy, qadam {h} (qoplanmaydi); birinchi origin "
          f"{y.index[boshlar[0]].date()}, oxirgi {y.index[boshlar[-1]].date()}")
    print(f"  SARIMA parametrlari 12 oyda bir qayta baholandi: {len(sarima.iter)} "
          f"marta; optimizator iteratsiyalari {sarima.iter}")

    print("\n=== 2. Nuqtali bashorat: MAE (GWh), oynalar bo'yicha ===")
    print(f"  {'usul':<18} {'MAE':>7} {'min':>6} {'max':>6}")
    for nom, v in mae.items():
        print(f"  {nom:<18} {v.mean():>7.2f} {v.min():>6.1f} {v.max():>6.1f}")

    print("\n=== 3. Juftlashgan farq (qator - seasonal naive), SE oynalar bo'yicha ===")
    for nom in list(usullar)[1:]:
        d = mae[nom] - mae["seasonal naive"]
        se = d.std(ddof=1) / np.sqrt(len(d))
        hukm = ("sezilarli yaxshi" if d.mean() < -2 * se else
                "sezilarli yomon" if d.mean() > 2 * se else "farq sezilarli emas")
        print(f"  {nom:<18} {d.mean():+7.2f}  SE {se:5.2f}  -> {hukm}")

    print("\n=== 4. Qaror: eng yaxshisidan sezilarli yomon bo'lmagan eng sodda ===")
    eng = min(mae, key=lambda k: mae[k].mean())
    print(f"  eng past MAE: {eng}")
    munosib = []
    for nom in usullar:                            # soddalik tartibida
        d = mae[nom] - mae[eng]
        se = d.std(ddof=1) / np.sqrt(len(d)) if nom != eng else 0.0
        if nom == eng or d.mean() <= 2 * se:
            munosib.append(nom)
        if nom != eng:
            print(f"  {nom:<18} {d.mean():+7.2f}  SE {se:5.2f}")
    print(f"  munosiblar: {munosib} -> tanlov: {munosib[0]}")

    print("\n=== 5. 95% intervallar: haqiqiy qamrov (60 nuqta) ===")
    print(f"  {'usul':<18} {'qamrov':>7} {'o_rtacha kenglik':>17}")
    for nom in usullar:
        print(f"  {nom:<18} {np.mean(ichida[nom]):>7.3f} "
              f"{np.mean(kenglik[nom]):>17.1f}")
    n = len(ichida["SARIMA"])
    print(f"  nominal 0.95; {n} nuqtada binomial SE ~ "
          f"{np.sqrt(0.95 * 0.05 / n):.3f} (nuqtalar mustaqil emas - aslida kattaroq)")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Backtest dizayni ===
  10 oyna, ufq 6 oy, qadam 6 (qoplanmaydi); birinchi origin 2021-01-01, oxirgi 2025-07-01
  SARIMA parametrlari 12 oyda bir qayta baholandi: 5 marta; optimizator iteratsiyalari [34, 14, 16, 26, 31]

=== 2. Nuqtali bashorat: MAE (GWh), oynalar bo'yicha ===
  usul                   MAE    min    max
  seasonal naive       53.35   23.9  114.8
  ETS(A,Ad,A)          37.27   12.7   84.1
  SARIMA               34.54   18.3   68.2
  ARIMAX (Ramazon)     34.51   19.4   75.4

=== 3. Juftlashgan farq (qator - seasonal naive), SE oynalar bo'yicha ===
  ETS(A,Ad,A)         -16.08  SE  7.00  -> sezilarli yaxshi
  SARIMA              -18.81  SE  7.07  -> sezilarli yaxshi
  ARIMAX (Ramazon)    -18.84  SE  6.18  -> sezilarli yaxshi

=== 4. Qaror: eng yaxshisidan sezilarli yomon bo'lmagan eng sodda ===
  eng past MAE: ARIMAX (Ramazon)
  seasonal naive      +18.84  SE  6.18
  ETS(A,Ad,A)          +2.76  SE  3.28
  SARIMA               +0.03  SE  3.53
  munosiblar: ['ETS(A,Ad,A)', 'SARIMA', 'ARIMAX (Ramazon)'] -> tanlov: ETS(A,Ad,A)

=== 5. 95% intervallar: haqiqiy qamrov (60 nuqta) ===
  usul                qamrov  o_rtacha kenglik
  seasonal naive       0.900             218.9
  ETS(A,Ad,A)          0.917             172.2
  SARIMA               0.967             174.9
  ARIMAX (Ramazon)     0.917             148.9
  nominal 0.95; 60 nuqtada binomial SE ~ 0.028 (nuqtalar mustaqil emas - aslida kattaroq)

Natija tahlili.

1-bo'lim — dizayn. 10 ta qoplanmaydigan 6 oylik oyna (2021-01 dan 2025-07 gacha originlar). Vaqt byudjeti uchun SARIMA parametrlari har 12 oyda qayta baholandi (5 marta), oraliqdagi oynalarda apply bilan faqat holat yangilandi; har yangi fit oldingi parametrlardan boshlandi. Optimizator iteratsiyalari: birinchi (sovuq) fitda 34, keyingilarida 14-31.

2-bo'lim — nuqtali bashorat. Seasonal naive 53.35 GWh, ETS(A,Ad,A) 37.27, SARIMA 34.54, ARIMAX 34.51. Oynalar orasidagi tarqoqlikka qarang: seasonal naive 23.9 dan 114.8 gacha — bir oynaga qarab xulosa qilish xavfli.

3-bo'lim — uchala klassik model seasonal naive dan sezilarli yaxshi: ETS -16.08 (SE 7.00), SARIMA -18.81 (SE 7.07), ARIMAX -18.84 (SE 6.18) — hammasida farq 2*SE dan katta. Bu yerda (28.1-darsdagi polinom modeldan farqli) murakkablik o'zini oqladi.

4-bo'lim — qaror. Eng past MAE — ARIMAX, lekin SARIMA undan atigi +0.03 (SE 3.53), ETS +2.76 (SE 3.28) orqada — ikkalasi ham sezilarli farq qilmaydi. Qoida "eng yaxshisidan sezilarli yomon bo'lmagan eng sodda" ETS(A,Ad,A) ni tanladi. Kutilmagan natija: 3-misolda ARIMAX Ramazon oylarida katta yutuq bergan edi, 10 oynali backtestda esa SARIMA dan farqi yo'q. Sabablari: 60 test oyining faqat bir nechtasida Ramazon 15 kundan ko'p; 2021-2025 yillarda Ramazon mart-may oylari atrofida qoldi va SARIMA ning mavsumiy qismi uni qisman "o'rgandi"; qolgan oylarda esa qo'shimcha parametr bahosi ozgina shovqin qo'shadi. Ramazon keyingi yillarda fevral-yanvarga o'tgani sari ARIMAX ning foydasi oshishi kutiladi — bu hozircha gipoteza, keyingi backtestlarda tekshiriladi. Qaror soddalik foydasiga: ETS tez, barqaror, tashqi ma'lumot quvuri talab qilmaydi.

5-bo'lim — intervallar. Nominal 0.95, haqiqiy qamrov: ETS 0.917, SARIMA 0.967, ARIMAX 0.917, seasonal naive (mavsumiy farqlar std si bo'yicha) 0.900. 60 nuqtada binomial SE ~`0.028, oyna ichidagi nuqtalar bog'liq bo'lgani uchun haqiqiy noaniqlik kattaroq — shuning uchun 0.917 ni "sezilarli past" deb e'lon qila olmaymiz, lekin yo'nalish odatiy: model intervallari biroz tor. Kenglikka ham qarang: ARIMAX intervali eng tor (148.9 GWh) — Ramazon effektini tushuntirgani uchun qoldiq dispersiyasi kichikroq, qamrovi esa ETS niki bilan bir xil. Seasonal naive eng keng (218.9`) va shunga qaramay qamrovi eng past — uning oddiy intervali trendni hisobga olmaydi. Amaliy xulosa: interval ham nuqtali bashorat kabi backtestda o'lchanadi, kerak bo'lsa empirik qamrov bo'yicha kalibrlanadi (28.3-darsda konformal usul).


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"SES trend va mavsumni ham kuzatadi" Faqat daraja; mavsumli qatorda seasonal naive dan ikki barobar yomon (156.22 va 77.50)
"Eng past AIC — eng yaxshi bashorat" AIC o'quvdagi moslik; ETS(A,N,A) AIC da birinchi, test MAE da orqada
"AIC bilan d ni ham tanlash mumkin" Turli d da likelihood turli ma'lumotda; natija birlikka bog'liq (MWh da 100% noto'g'ri)
"Ljung-Box p katta — model to'g'ri" Faqat tuzilish topilmadi; guruhlab qarang (Ramazon oylari)
"95% interval 95% qamraydi" Faqat model to'g'ri va parametrlar aniq bo'lsa; backtestda o'lchang
"Damped trend har doim xavfsiz" Trend haqiqatan davom etsa, uzoq ufqda orqada qoladi (65.12 va 49.14)
"Tashqi o'zgaruvchi har doim yordam beradi" Bitta oynada katta yutuq, 10 oynada farq +0.03 (SE 3.53)
"Xato turi (A/M) bashoratni o'zgartiradi" Nuqtali bashorat deyarli bir xil; intervallar va likelihood o'zgaradi
"exp(log bashorat) — o'rtacha bashorat" Mediana bashorati; o'rtacha biroz yuqoriroq
"Backtestda har oynada to'liq setka kerak" Tartib bir marta; parametrlar har N oynada; apply va issiq start

6. Keng tarqalgan xatolar va yechimlari

1. AIC ni turli d orasida solishtirish

python
eng = min([ARIMA(x, order=(1, 0, 0)).fit(), ARIMA(x, order=(0, 1, 0)).fit()],
          key=lambda r: r.aic)                                        # ⚠️
d = 1 if adfuller(x, result_object=False)[1] >= 0.05 else 0          # ✅ avval d,
eng = min([ARIMA(x, order=(p, d, q)).fit() for p in range(3)          #    keyin AIC
           for q in range(3)], key=lambda r: r.aic)

2. Qoldiq testida model_df va boshlang'ich qoldiqlar

python
acorr_ljungbox(r.resid, lags=[24])                                    # ⚠️
acorr_ljungbox(r.resid.iloc[1 + 12:], lags=[24], model_df=p + q + P + Q)   # ✅

3. Mavsumli qatorga SES/Holt

python
ExponentialSmoothing(y, trend="add").fit()                            # ⚠️
ExponentialSmoothing(y, trend="add", seasonal="mul", seasonal_periods=12).fit()  # ✅

4. Intervalga tekshirmasdan ishonish

python
past, yuqori = f.conf_int(alpha=0.05).T.to_numpy()                   # ⚠️ va'da
qamrov = np.mean((haq >= past) & (haq <= yuqori))                     # ✅ backtestda

5. Kelajakda noma'lum exog

python
SARIMAX(y, exog=haqiqiy_harorat).fit()      # backtestda haqiqiy harorat  ⚠️
SARIMAX(y, exog=harorat).fit()              # backtestda harorat BASHORATI ✅

6. Har oynada to'liq qayta baholash va setka

python
for o in boshlar: eng = setka_qidir(y[:o])                           # ⚠️ soatlab
r = SARIMAX(y[:o], order=tanlangan, ...).fit(start_params=oldingi)   # ✅ har 12 oyda
r = r.apply(y[:o_keyingi])                                          #    oraliqda

7. exp(log prognoz) ni o'rtacha deb hisobot berish

python
prognoz = np.exp(f.predicted_mean)                   # ⚠️ bu mediana
prognoz = np.exp(f.predicted_mean + 0.5 * sigma2)    # ✅ taxminiy o'rtacha (lognormal)

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 11.1, 11.5-darslar (o'tilgan): nol gipoteza va chi-kvadrat — Ljung-Box statistikasi chi^2 taqsimotga tayanadi
  • 13.2, 13.4-darslar (o'tilgan): regressiya va qoldiqlar diagnostikasi — ARIMAX "regressiya + ARIMA xato" ko'rinishida
  • 18.10-dars (o'tilgan): juftlashgan taqqoslash — backtest oynalari bo'yicha SE
  • 27.11-27.13-darslar (o'tilgan): monitoring va qayta o'qitish — bashorat modeli ham muntazam backtest va qamrov kuzatuvini talab qiladi
  • 28.1-dars (o'tilgan): dekompozitsiya, statsionarlik, ACF/PACF, bazaviylar — tartib tanlashning asosi
  • 28.3-dars: ML bilan bashorat — lag belgilar, global modellar, kvantil va konformal intervallar; klassik modellar u yerda raqib bo'ladi

8. Eng yaxshi amaliyotlar

  1. Avval log va dekompozitsiya; mavsum bo'lsa — faqat mavsumli modellar (HW, ETS(.,.,A), SARIMA).

  2. d va D ni testlar va domen bilimi bilan tanlang; AIC ni faqat shu ichida ishlating.

  3. Kichik setka, ACF/PACF dan boshlab; dAIC < 2 bo'lsa soddasini oling.

  4. Qoldiqlarni Ljung-Box va guruhlash (bayram, oy) bilan tekshiring.

  5. Intervallarning haqiqiy qamrovi va kengligini backtestda o'lchang.

  6. Tashqi o'zgaruvchi faqat kelajakda ma'lum bo'lsa; backtestda aynan o'sha paytda ma'lum bo'lgan qiymat bilan.

  7. Vaqt byudjetini hisoblang: parametrlarni kamroq qayta baholash, apply, issiq start.

  8. Seasonal naive bilan juftlashgan backtest; eng yaxshisidan sezilarli yomon bo'lmagan eng sodda.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # SES da alfa = 1 bo'lsa, bashorat nimaga teng?
2.  # alfa = 0.3 bo'lsa, oxirgi kuzatuv va undan oldingisining vaznlari?
3.  # Holt ning 24 oylik bashorati qanday shaklda?
4.  # ARIMA(0,1,0) nimaning boshqa nomi?
5.  # ARIMA(1,0,0) + const ning uzoq ufqdagi bashorati?
6.  # farqlangan qatorning ACF i 1 va 12-lagda manfiy cho'qqi - qaysi model?
7.  # AIC (1,1,1) = -590, AIC (1,0,1) = -620 - (1,0,1) yaxshiroqmi?
8.  # Ljung-Box p = 0.001 nimani bildiradi?
9.  # SARIMA(1,1,1)(0,1,1)12 da Ljung-Box uchun model_df?
10. # 95% interval backtestda 0.81 qamradi - nima deysiz?
11. # ARIMAX ga backtestda haqiqiy haroratni berish - muammo nima?
12. # 60 oyna x 16 model x 1.5 s - qancha vaqt?
Javoblar
  1. Oxirgi kuzatuvga — naive bashorat
  2. 0.3 va 0.3 * 0.7 = 0.21
  3. To'g'ri chiziq — trend cheksiz davom etadi
  4. Tasodifiy yurish (naive bashorat)
  5. Qator o'rtachasiga geometrik yaqinlashadi
  6. MA(1) va mavsumiy MA(1) — "airline" SARIMA(0,1,1)(0,1,1)12
  7. Bilib bo'lmaydi — d har xil, AIC lar solishtirilmaydi
  8. Qoldiqda avtokorrelyatsiya qolgan — model yetishmaydi
  9. p + q + P + Q = 1 + 1 + 0 + 1 = 3
  10. Intervallar tor — model farazlari buzilgan; kalibrlash (konformal) yoki modelni qayta ko'rish kerak
  11. Ishlab chiqarishda faqat harorat bashorati bo'ladi — backtest optimistik (sizish)
  12. 60 * 16 * 1.5 = 1440 s = 24 daqiqa

Vazifa 2: Xatolarni tuzating

python
1.  model = ExponentialSmoothing(oylik_savdo).fit()     # mavsumli qator
    prognoz = model.forecast(12)

2.  a = ARIMA(y, order=(1, 0, 1)).fit()
    b = ARIMA(y, order=(1, 1, 1)).fit()
    eng = a if a.aic < b.aic else b

3.  lb = acorr_ljungbox(r.resid, lags=[24])            # SARIMA(1,1,1)(0,1,1)12

4.  print("95% interval:", f.conf_int(alpha=0.05))     # hisobotda kafolat sifatida

5.  for o in boshlar:                                    # 60 oyna
        eng = min(setka_16_model(ly[:o]), key=lambda r: r.aic)
Javoblar
python
1.  model = ExponentialSmoothing(oylik_savdo, trend="add", damped_trend=True,
                                 seasonal="mul", seasonal_periods=12).fit()

2.  # avval d ni tanlang (ADF + KPSS, domen), keyin AIC faqat shu d ichida;
    # shubhada ikkala d ni rolling-origin backtestda solishtiring

3.  lb = acorr_ljungbox(r.resid.iloc[13:], lags=[24], model_df=3)

4.  # backtestda empirik qamrovni o'lchang va uni ham hisobotga yozing

5.  # tartibni bir marta tanlang; parametrlarni har 12 oyda (issiq start),
    # oraliqda r.apply(ly[:o])

Vazifa 3: Eksponensial silliqlash

Modellang (1-misol asosida):

  1. Holt ni noldan yozing (l_t, b_t) va ExponentialSmoothing(trend="add") bilan bir xil alfa, beta da solishtiring
  2. alfa ni 0.05, 0.2, 0.5, 0.9 qilib, ehtiyot qism talabida bir qadamli MAE ni chizing (ASCII jadval)
  3. Test oynasini 24 dan 12 va 36 ga o'zgartirib, damped va damped bo'lmagan trendning farqi qanday o'zgarishini o'lchang
  4. ETS(A,Ad,A) ning 80% va 95% intervallarini chiqarib, kengliklar nisbatini 1.28 / 1.96 bilan solishtiring

Vazifa 4: ARIMA

Modellang (2-misol asosida):

  1. AR(1) phi = 0.5, 0.9, 0.99 uchun ADF ning "to'g'ri javob" ulushini n = 150 da o'lchang
  2. ARIMA(0,1,1) va SES bir xil bashorat berishini ko'rsating (theta = alfa - 1)
  3. 6-bo'limga "ko'p oynali ichki backtest" qoidasini qo'shing (5 oyna) va d tanlov ulushini solishtiring
  4. y va log y da o'qitilgan modellarning AIC larini solishtirish nega noto'g'ri — raqam bilan ko'rsating

Vazifa 5: SARIMA va ARIMAX

Modellang (3-misol asosida):

  1. Setkaga P = 1 ni qo'shing (8 model) — AIC va test MAE qanday o'zgaradi? Vaqtni ham hisobga oling
  2. Ramazon effektini 0.004 ga oshiring va ARIMAX yutug'ini qayta o'lchang
  3. Exog ni "Ramazon bor/yo'q" (0/1) qiling — kunlar sonidan yomonroqmi?
  4. Qurbon hayiti uchun ikkinchi exog qo'shing (sintetik taqvimda)

Vazifa 6: Backtest va qamrov

Modellang (4-misol asosida):

  1. Ufqni 12 oy, qadamni 12 qilib (5 oyna) natijani takrorlang — SE qanday o'zgaradi?
  2. Parametrlarni har oynada qayta baholang (har=1) — MAE o'zgaradimi, vaqt qancha oshadi?
  3. Qamrovni ufq bo'yicha ajrating (h = 1-2 va h = 5-6)
  4. Empirik kalibrlash: o'quv backtestidan qoldiqlarning 95-kvantilini olib, intervalni shunga kengaytiring va qamrovni qayta o'lchang

Vazifa 7: O'ylash

Tahlilchi: "Men SARIMA, ETS va Prophet ni sinab ko'rdim, oxirgi 12 oyda SARIMA eng past MAPE berdi (3.1%). Modelni tanladim, 95% intervallarni ham hisobotga qo'shdim." Rahbar shartnoma hajmini intervalning yuqori chegarasiga qarab belgilamoqchi. Nima deysiz?

Javob

Qisqa javob: tanlov jarayoni ham, interval ham hali ishonchli emas; shartnoma hajmini bunga bog'lashdan oldin uchta tekshiruv kerak.

1. Bitta oyna — tanlov uchun kam. 12 oy — bitta test oynasi. 4-misolda oynalar orasida xato 5 barobar farq qildi (seasonal naive 23.9 dan 114.8 gacha), 1-misolda esa bitta oynada eng yaxshi ko'ringan model AIC bo'yicha ham, keyingi oynalarda ham boshqacha bo'lishi mumkin edi. Kerak: kamida 8-10 ta qoplanmaydigan oyna, seasonal naive bazaviy, juftlashgan farq va SE. Farq sezilarli bo'lmasa — eng sodda model.

2. Metrika. MAPE past bashoratni afzal ko'radi (28.1, 4-misol) — shartnoma uchun bu xavfli yo'nalish (tanqislik). MAE (GWh) va xato narxining asimmetriyasi bo'yicha baholash kerak: tanqislik narxi ortiqcha xarid narxidan qancha katta?

3. Interval — va'da, o'lchov emas. Model intervali model to'g'ri va parametrlar aniq degan farazga tayanadi. 4-misolda "95%" intervallar backtestda 0.90-0.97 qamradi, bunda eng tor interval eng yaxshi qamrovni bermadi. Shartnoma yuqori chegaraga qarab tuzilsa, bizni aynan yuqori dumdagi qamrov qiziqtiradi: "haqiqiy qiymat yuqori chegaradan necha marta oshdi?" — buni backtestda alohida sanash kerak.

python
# 1) 10 oyna x 6 oy: seasonal naive, ETS, SARIMA, (Prophet)
# 2) MAE + juftlashgan SE; qoida oldindan yozilgan
# 3) qamrov: umumiy va "yuqori chegaradan oshish" ulushi, ufq bo'yicha
# 4) kerak bo'lsa empirik (konformal) kalibrlash - 28.3

Rahbarga javob: "SARIMA bitta yilda yaxshi natija berdi, lekin bu tasodif bo'lishi mumkin. Bir hafta ichida besh yillik backtest qilaman: modellar va oddiy 'o'tgan yil shu oy' qoidasi bir xil oynalarda. Yuqori chegara uchun esa model va'dasini emas, tarixda haqiqatan necha marta oshib ketganini o'lchab beraman — shartnomani o'sha empirik chegaraga, tanqislik narxini hisobga olib bog'laymiz."

Nimani mustahkamlaydi: 2.6, 2.8, 2.9, 2.10-bo'limlar.


Xulosa

Bu darsda klassik bashorat modellarini qurish, tanlash, tekshirish va halol baholashni o'rgandik.

Eng muhim uch fikr:

  1. Eksponensial silliqlash va ARIMA — tuzilishni tushunadigan modellar. 1-misolda SES noldan statsmodels bilan aynan bir xil alfa = 0.1419 berdi; mavsumni bilmagan SES va Holt oylik elektrda seasonal naive dan ikki barobar yomon bo'ldi, Holt-Winters esa undan ancha yaxshi. 2-misolda ACF/PACF imzolari va AIC setkasi haqiqiy tartibni tikladi, Ljung-Box yetishmaydigan modelni (p = 0.0000) ushladi. 3-misolda qoldiqlarni guruhlash oylik mavsum ushlay olmagan Ramazon effektini ko'rsatdi, ARIMAX esa uni 0.00209 +- 0.00026 (haqiqiy 0.00200) bilan baholadi.

  2. AIC — faqat bir xil ma'lumotda. Turli d da likelihood turli miqdordagi kuzatuvlardan hisoblanadi: 2-misolda bir xil statsionar qatorlarda AIC GWh da 50%, MWh da 100% hollarda noto'g'ri d = 1 ni tanladi — javob o'lchov birligiga bog'liq bo'lib qoldi. d — testlar, domen va backtest bilan; AIC — shu d ichida. Bitta test oynasi ham ishonchsiz hakam: AIC bo'yicha birinchi ETS varianti testda orqada qoldi.

  3. Backtest nuqtali bashoratni ham, intervalni ham o'lchaydi. 4-misolda 10 oynada ETS, SARIMA va ARIMAX seasonal naive dan sezilarli yaxshi chiqdi, lekin o'zaro sezilarli farq qilmadi — qoida eng sodda ETS(A,Ad,A) ni tanladi; bitta oynada katta yutuq bergan ARIMAX ko'p oynada SARIMA dan farq qilmadi. "95%" intervallar haqiqatda 0.90-0.97 qamradi. Vaqt byudjeti uchun parametrlarni har 12 oyda qayta baholash va apply bilan holatni yangilash backtestni amaliy qildi.

Keyingi darsda ML bilan vaqt qatori bashorati: bashoratni supervised vazifaga aylantirish (lag, rolling statistikalar va shift tuzog'i, kalendar va bayram belgilari), ko'p do'konli global model, ko'p qadamli bashorat strategiyalari (rekursiv, to'g'ridan-to'g'ri, ko'p chiqishli), HistGradientBoosting ni seasonal naive va ETS ga qarshi juftlashgan backtest, kvantil regressiya va konformal intervallar, ierarxik bashorat g'oyasi.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
28.2-dars: Klassik bashorat modellari — IlmHamroh