IlmHamroh
Data Science va sun'iy intellekt/Maxsus mavzular1/12-dars50 daqiqa
Mundarija (25)

28.1-dars: Vaqt qatorlari asoslari

28-QISM — MAXSUS MAVZULAR · 1-dars


1. Kirish va motivatsiya

27-qism oxirida aytganimizdek, bu qismda kursning asosiy yo'lidan tashqarida qolgan, lekin amalda tez-tez uchraydigan vazifa va ma'lumot turlarini ko'ramiz. Ularning har birida shu kursda qurilgan skelet — baholash dizayni, juftlashgan taqqoslash, "eng sodda munosib model" qoidasi — yangi sharoitga qo'llanadi. Birinchi va, ehtimol, eng keng tarqalgani — vaqt qatorlari: kunlik savdo, soatlik elektr iste'moli, haftalik taksi buyurtmalari, oylik bank tranzaksiyalari. Deyarli har bir kompaniyada kimdir "keyingi oyda qancha bo'ladi?" degan savolga javob berishi kerak.

Birinchi qarashda bu oddiy regressiya: y — savdo, belgilar — sana, hafta kuni, oy. Lekin vaqt qatori kursda shu paytgacha ko'rgan jadvallardan bitta muhim jihati bilan farq qiladi: qatorlar mustaqil emas va ularning tartibi ma'lumotning bir qismi. Bugungi savdo kechagisiga o'xshaydi; o'tgan shanbadagi savdo bu shanbani bashorat qilishga yordam beradi; biz esa har doim kelajakni — o'quv ma'lumoti chegarasidan tashqaridagi vaqtni — bashorat qilamiz. Kursning asosiy qismida qo'llagan ko'p odatlar (aralashtirib bo'lish, tasodifiy KFold, "qatorlar i.i.d." degan faraz) bu yerda jimgina buziladi va juda optimistik raqamlar beradi.

Real vaziyat. Samarqanddagi oziq-ovqat do'konlari tarmog'i ombor buyurtmalarini avtomatlashtirmoqchi bo'ldi. Tahlilchi kunlik savdoga gradient boosting o'qitdi: belgilar — kun raqami, hafta kuni, yil kuni. KFold(5, shuffle=True) bilan baholadi — o'rtacha xato atigi 6%. Model ishga tushdi va birinchi oyning o'zidayoq xato 10-11% ga chiqdi: omborda bir kunlari mahsulot yetmadi, bir kunlari ortib qoldi. Muammo modelda emas, baholashda edi: aralash bo'lishda test kunining ikki tomonidagi qo'shni kunlar o'quvda bor — model "kelajakni" emas, "o'tmish ichidagi bo'shliqni" to'ldirgan. Ustiga-ustak, hech kim model oddiy "o'tgan haftani takrorla" qoidasidan yaxshiroqmi, deb tekshirmagan edi.

Bu darsda vaqt qatorining tuzilishini tushunishni, uni to'g'ri baholashni va har qanday modelni oddiy bazaviylar bilan halol solishtirishni o'rganamiz. Keyingi ikki dars (klassik modellar va ML bilan bashorat) aynan shu poydevorga quriladi.

Bu darsda:

  • Vaqt qatori nima va nega oddiy regressiyadan farq qiladi: tartib, avtokorrelyatsiya, ekstrapolyatsiya
  • Komponentlar: trend, mavsumiylik (kunlik, haftalik, yillik, ko'p mavsumiylik), tsikl, shovqin, bayram effektlari
  • Dekompozitsiya: klassik va STL/MSTL, qo'shiluvchi va ko'paytiruvchi, log almashtirish
  • Statsionarlik: ADF va KPSS — qarama-qarshi nol gipotezalar; farqlash va mavsumiy farqlash
  • ACF va PACF: AR va MA imzolari
  • Baholash: tasodifiy bo'lish sizishi (o'lchab), vaqt bo'yicha bo'lish, rolling-origin backtest
  • Metrikalar: MAE, RMSE, MAPE va uning nol atrofidagi muammosi, sMAPE, MASE
  • Bazaviylar: naive, seasonal naive, o'rtacha, drift
  • Resampling va bo'sh sanalarni to'ldirish (qisqa)

ℹ Misollar real numpy/pandas/statsmodels/sklearn bilan (Python 3.14). Ma'lumot sintetik, urug' bilan yaratiladi; "bayram kunlari" — sintetik taqvimda belgilangan sanalar.


2. Nazariya — chuqur tushuntirish

2.1. Vaqt qatori nima va nega u "oddiy jadval" emas

Vaqt qatori — bir xil oraliqda (soat, kun, hafta, oy) o'lchangan qiymatlar ketma-ketligi: y_1, y_2, ..., y_T. Pandas da bu DatetimeIndex li Series (3.12-dars); oraliq freq bilan beriladi ("D", "h", "W-SUN", "MS").

Kursning asosiy qismida qatorlarni almashtiriladigan deb faraz qildik: qatorlarni aralashtirsak, hech narsa o'zgarmaydi. Vaqt qatorida bu faraz uch joyda buziladi:

text
1. TARTIB MA'LUMOTNING BIR QISMI
   qatorlarni aralashtirsangiz - trend, mavsum, "kecha" tushunchasi yo'qoladi

2. AVTOKORRELYATSIYA (qatorlar mustaqil emas)
   corr(y_t, y_{t-1}) katta: bugungi savdo kechagiga o'xshaydi
   corr(y_t, y_{t-7}) katta: shanba o'tgan shanbaga o'xshaydi
   -> i.i.d. farazi YO'Q; standart xatolar, CV, testlar buziladi

3. BASHORAT - DOIM EKSTRAPOLYATSIYA
   o'quv:   [------------ 2023 ... 2025-09 ------------]
   bashorat:                                            [2025-10 ... ]
   kelajak har doim o'quv hududidan TASHQARIDA (vaqt o'qi bo'yicha)
   -> trendni davom ettira olmaydigan model (daraxt) muammoga duch keladi

Shu uch sababdan vaqt qatori o'z vositalarini talab qiladi: tuzilishini ko'rish uchun dekompozitsiya, xotira tuzilishini ko'rish uchun ACF/PACF, halol baho uchun vaqt bo'yicha backtest, "model umuman kerakmi?" degan savol uchun bazaviylar.

Vaqt qatorida birinchi savol "qaysi model?" emas, "qanday baholayman va nimaga nisbatan?" — noto'g'ri baholangan eng yaxshi model ham omborni bo'shatib qo'yadi.

2.2. Komponentlar

Deyarli har qanday biznes qatorini bir necha bo'lakka ajratib tasavvur qilish mumkin:

text
y_t = TREND_t  +  MAVSUM_t  +  (TSIKL_t)  +  KALENDAR_t  +  SHOVQIN_t      (qo'shiluvchi)
y_t = TREND_t  *  MAVSUM_t  *  ...                         *  SHOVQIN_t      (ko'paytiruvchi)

TREND      uzoq muddatli yo'nalish: do'kon mashhurlashadi, shahar o'sadi
MAVSUM     QAT'IY davr bilan takrorlanadi:
             kunlik  (soatlik ma'lumotda: kechqurun elektr cho'qqisi, davr 24)
             haftalik (kunlik ma'lumotda: shanba savdosi yuqori, davr 7)
             yillik  (kunlik: yozda muzqaymoq, qishda isitish, davr ~365.25)
           KO'P MAVSUMIYLIK: soatlik elektr = 24 + 168 + 8766 davrlar birga
TSIKL      takrorlanadi, lekin davri QAT'IY EMAS (iqtisodiy sikl, 2-7 yil)
           -> mavsumdan farqi: kalendar bilan bashorat qilib bo'lmaydi
KALENDAR   bayramlar, qisqa kunlar, maosh kuni; HARAKATLANUVCHI sanalar:
           Ramazon hayiti har yili ~11 kun oldinga siljiydi ->
           yillik mavsum uni USHLAMAYDI, alohida belgi kerak
SHOVQIN    qolgan hamma narsa; ko'pincha o'zi ham avtokorrelyatsiyalangan

Qo'shiluvchi yoki ko'paytiruvchi? Savol oddiy: mavsum tebranishi darajaga bog'liqmi? Do'kon savdosi ikki barobar o'ssa, shanba va dushanba orasidagi farq ham ikki barobar o'sadi — bu ko'paytiruvchi tuzilish. Harorat esa har yili taxminan bir xil amplituda bilan tebranadi — qo'shiluvchi. Amaliy qoida: musbat, o'sib boradigan biznes qatorlari (savdo, buyurtmalar, iste'mol) odatda ko'paytiruvchi; ularni log ga o'tkazsangiz, qo'shiluvchi bo'ladi:

text
log y_t = log TREND_t + log MAVSUM_t + log SHOVQIN_t

Log almashtirishning qo'shimcha foydasi: dispersiya barqarorlashadi, diff(log y) taxminan foizli o'zgarish bo'ladi. Qatorda nol bo'lsa — log1p yoki Box-Cox ga o'xshash almashtirishlar.

2.3. Dekompozitsiya: klassik va STL

Klassik dekompozitsiya (statsmodels.tsa.seasonal.seasonal_decompose):

text
1. TREND   = markazlashgan harakatlanuvchi o'rtacha (davr m; juft m da 2 x m)
2. MAVSUMSIZ = y - TREND   (ko'paytiruvchida y / TREND)
3. MAVSUM  = har mavsum pozitsiyasi (masalan, har hafta kuni) bo'yicha
             o'rtacha, keyin markazlash (yig'indi 0 yoki ko'paytma 1)
4. QOLDIQ  = y - TREND - MAVSUM

KAMCHILIKLARI:
  chetlarda m/2 ta NaN (trend yo'q) - oxirgi kunlar uchun eng kerakli joyda!
  MAVSUM butun qator uchun DOIMIY - vaqt bilan o'zgarsa qoldiqqa tushadi
  outlier (bayram) o'rtachani siljitadi
  faqat BITTA mavsum

STL (Seasonal-Trend decomposition using Loess) — mahalliy silliqlash (Loess) bilan ishlaydi:

text
STL(y, period=7, robust=True)
  mavsum vaqt bilan SEKIN o'zgarishi mumkin (seasonal oynasi bilan)
  robust=True: katta qoldiqlarga (bayram, xato) kichik vazn beradi
  chetlarda ham trend bor (NaN yo'q)
MSTL(y, periods=(7, 365))       - ko'p mavsumiylik: har davr navbat bilan
                                  (davr qator uzunligining yarmidan kichik bo'lsin)

Dekompozitsiyadan keyin komponent kuchini son bilan baholash foydali (R bo'yicha Hyndman ta'rifi):

text
F_trend = max(0, 1 - Var(R) / Var(T + R))
F_mavsum = max(0, 1 - Var(R) / Var(S + R))
  0 ga yaqin - komponent deyarli yo'q; 1 ga yaqin - kuchli

Dekompozitsiya — bashorat emas, tushunish vositasi. U qaysi mavsum borligini, ko'paytiruvchimi yoki yo'qligini, qoldiqda nima qolganini (bayramlar!) ko'rsatadi. Bu qarorlar keyingi darsdagi modellarning sozlamalariga aylanadi.

2.4. Statsionarlik va ikki test

Qator (kuchsiz) statsionar, agar uning o'rtachasi, dispersiyasi va avtokovariatsiyasi vaqtga bog'liq bo'lmasa: E[y_t] = mu, Var(y_t) = sigma^2, Cov(y_t, y_{t-k}) faqat k ga bog'liq. Trend (o'rtacha o'zgaradi) va mavsum (o'rtacha davriy o'zgaradi) statsionarlikni buzadi. ARIMA kabi klassik modellar statsionar qatorni talab qiladi, shuning uchun avval qatorni statsionar holga keltiramiz.

Nostatsionarlikning ikki turi bor va ular turli davolanadi:

text
TREND-STATSIONAR:     y_t = a + b*t + (statsionar shovqin)
  shok o'tkinchi - qator trend chizig'iga QAYTADI
  davo: trendni ayirish (detrend)

BIRLIK ILDIZ (farq-statsionar): y_t = y_{t-1} + e_t   (tasodifiy yurish)
  shok ABADIY qoladi - qaytadigan chiziq yo'q
  davo: farqlash  diff(y)_t = y_t - y_{t-1}

Ikki mashhur test qarama-qarshi nol gipotezalar bilan ishlaydi:

text
ADF (Augmented Dickey-Fuller):
  H0: birlik ildiz BOR (statsionar EMAS)
  p < 0.05  -> H0 rad etiladi -> "statsionar"
  p katta   -> "statsionar ekanini isbotlay olmadik" (dalil yetmadi!)

KPSS (Kwiatkowski-Phillips-Schmidt-Shin):
  H0: statsionar
  p < 0.05  -> "statsionar EMAS"
  p katta   -> "statsionar emasligiga dalil yo'q"

regression="c"  : o'zgarmas atrofida statsionarlik
regression="ct" : chiziqli trend atrofida (trend-statsionarlik)

Ikkalasini birga ishlatish — to'rt holat:

ADF KPSS Xulosa
rad etdi (p < 0.05) rad etmadi (p >= 0.05) statsionar — ikkala test rozi
rad etmadi rad etdi statsionar EMAS — ikkala test rozi
rad etmadi rad etmadi dalil yetarli emas (qisqa qator, quvvat past)
rad etdi rad etdi ziddiyat — ko'pincha trend-statsionar yoki struktura o'zgarishi; ct varianti, grafik va farqlash bilan chuqurroq qarang

"p katta" hech qachon "H0 isbotlandi" degani emas (11.1-dars). Shuning uchun ikki qarama-qarshi testni birga ishlatamiz: faqat ikkalasi rozi bo'lganda xulosamiz ishonchliroq. Qolgan hollarda — grafik, dekompozitsiya va domen bilimi.

KPSS p-qiymatini jadvaldan interpolyatsiya qiladi va faqat [0.01, 0.10] oralig'ida beradi: 0.010 aslida "0.01 dan kichik", 0.100 — "0.10 dan katta". Chegaradan chiqqanda statsmodels InterpolationWarning beradi.

ℹ statsmodels ogohlantirishlari haqida. statsmodels ko'p holatda stderr ga ogohlantirish chiqaradi: InterpolationWarning (KPSS), ConvergenceWarning (optimizator), ValueWarning (indeksda freq yo'q), FutureWarning (API o'zgarishi). Bu darslarda indeksga har doim freq beramiz (pd.date_range(..., freq="D")) va ma'lum, tushunilgan ogohlantirishlarni with warnings.catch_warnings(): warnings.simplefilter("ignore") bilan faqat shu chaqiruv atrofida o'raymiz. Ogohlantirishni butun dastur bo'yicha o'chirmang — ConvergenceWarning haqiqiy muammoni bildirishi mumkin.

2.5. Farqlash va mavsumiy farqlash

text
birinchi farq:        d1_t = y_t - y_{t-1}           trend / birlik ildizni oladi
mavsumiy farq (m):    dm_t = y_t - y_{t-m}           mavsumni oladi (m = 7, 12, 24)
ikkalasi:             (1 - B)(1 - B^m) y_t           B - orqaga siljitish operatori

log + farq:           diff(log y)_t ~ (y_t - y_{t-1}) / y_{t-1}   (foizli o'zgarish)

ORTIQCHA FARQLASH:
  statsionar qatorni yana farqlasangiz - dispersiya OSHADI
  oq shovqin e_t ni farqlasak: Var(e_t - e_{t-1}) = 2 * sigma^2
  ACF(1) ~ -0.5 ga yaqin manfiy qiymat - ortiqcha farqlash belgisi
  qoida: statsionarlikka yetgan eng KAM farqlashni tanlang

Kunlik savdo uchun diff(1) trendni oladi, lekin haftalik mavsum qoladi: bugungi shanba kechagi jumadan baland, ertangi yakshanba bugungidan past — farq ham haftalik tebranadi. diff(7) esa bir vaqtda ham trendni (taxminan), ham haftalik mavsumni oladi.

2.6. ACF va PACF

text
ACF(k)  = corr(y_t, y_{t-k})                         "k kun oldingi bilan o'xshashlik"
          noldan: sum (y_t - ybar)(y_{t-k} - ybar) / sum (y_t - ybar)^2
PACF(k) = corr(y_t, y_{t-k} | y_{t-1}, ..., y_{t-k+1})
          oraliq lag larning ta'siri OLIB TASHLANGANDAN keyingi bog'liqlik

95% chegara (oq shovqin uchun):  +-1.96 / sqrt(n)
  n = 500 -> +-0.088;  chegaradan chiqqan lag - "sezilarli" (20 ta lagdan
  ~1 tasi tasodifan chiqadi - ko'p testlash!)

IMZOLAR:                  ACF                     PACF
  AR(p)            sekin (geometrik) so'nadi    p dan keyin UZILADI
  MA(q)            q dan keyin UZILADI          sekin so'nadi
  ARMA             ikkalasi ham so'nadi         ikkalasi ham so'nadi
  birlik ildiz     juda sekin, deyarli chiziqli kamayadi
  mavsum m         m, 2m, 3m lagda cho'qqilar

Bu jadval keyingi darsda ARIMA tartibini tanlashning boshlang'ich nuqtasi bo'ladi. Regressiya qoldiqlaridagi avtokorrelyatsiyani 13.4-darsda Durbin-Watson bilan ko'rgan edik — ACF uning barcha lag lar bo'yicha kengaytmasi.

2.7. Baholash: tasodifiy bo'lish nega xato

Vaqt qatorida KFold(shuffle=True) ikki yo'l bilan aldaydi:

text
1. INTERPOLYATSIYA vs EKSTRAPOLYATSIYA
   aralash bo'lish:  o'quv ###.##.###.#.##  test - bo'shliqlar ichida
                     test kunining IKKI tomonidagi qo'shnilar o'quvda
                     -> model "bo'shliqni to'ldiradi" (oson)
   haqiqat:          o'quv ##############|  test - chegaradan keyin
                     -> model kelajakni "ko'rmasdan" aytadi (qiyin)

2. KELAJAK MA'LUMOTI O'TMISHGA SIZADI
   2025 yil ma'lumoti bilan o'qitilgan model 2024 ni "bashorat qiladi"
   - mahalliy daraja (raqobatchi ochildi, ta'mir) kelajakdan ma'lum
   - avtokorrelyatsiya: test kuni o'quvdagi qo'shnisiga o'xshaydi

Natija — optimistik baho. Qanchalik optimistik — ma'lumotga bog'liq; 3-misolda buni uchta do'konda o'lchaymiz. To'g'ri yo'l — faqat o'tmishda o'qitib, kelajakda sinash. 18.2-darsdagi TimeSeriesSplit aynan shunday; bashorat qilish amaliyotida uning umumiy shakli rolling-origin backtest deyiladi:

text
KENGAYUVCHI OYNA (expanding):
  oyna 1:  [oooooooooooo]tttt
  oyna 2:  [oooooooooooooooo]tttt
  oyna 3:  [oooooooooooooooooooo]tttt
  butun tarix ishlatiladi; eski rejim ham o'quvda qoladi

SIRPANUVCHI OYNA (sliding / rolling):
  oyna 1:  [oooooooooooo]tttt
  oyna 2:      [oooooooooooo]tttt
  oyna 3:          [oooooooooooo]tttt
  faqat oxirgi W kun; dunyo o'zgarsa tezroq moslashadi, lekin ma'lumot kam
  (W >= 1 yil bo'lsin, aks holda yillik mavsum ko'rinmaydi)

o - o'quv, t - test (UFQ = h kun); "origin" - bashorat qilingan payt

Dizayn qarorlari (natijani ko'rishdan oldin):

  • Ufq h — biznes savoliga mos: ombor 14 kun oldin buyurtma bersa, h = 14. Bir qadamli xato 14 kunlik xatodan ancha kichik — ularni aralashtirmang.
  • Oynalar soni — ko'p oyna = kichik SE, lekin ko'proq hisob. 10-20 oyna odatda yetarli; test oynalari bir-birini qoplamasin (qadam >= h), aks holda xatolar korrelyatsiyalanadi va SE kichik chiqadi.
  • Gap — ma'lumot kechikib kelsa (kecha savdosi bugun kechqurun ma'lum), o'quv va test orasida bo'shliq qoldiring (TimeSeriesSplit(gap=...)).
  • Juftlashgan taqqoslash — barcha modellar bir xil oynalarda baholanadi; farqni oynalar bo'yicha d.mean() va SE = d.std(ddof=1)/sqrt(k) bilan solishtiramiz (18.10-dars); |farq| > 2*SE — sezilarli.

2.8. Metrikalar

12.8-darsda regressiya metrikalarini ko'rgan edik; vaqt qatorida ularga ikki muhim qo'shimcha bor:

text
MAE   = mean |y - f|                   qator birligida ("kuniga 20 chek")
RMSE  = sqrt(mean (y - f)^2)           katta xatolarni jazolaydi; >= MAE
MAPE  = mean |y - f| / |y| * 100       foizda; y = 0 da CHEKSIZ;
                                       past prognozni afzal ko'radi
sMAPE = mean |y - f| / ((|y|+|f|)/2) * 100
                                       0..200%; y = 0 va f > 0 da 200%
WAPE  = sum |y - f| / sum |y|          nollarga chidamli "vaznli MAPE"
MASE  = MAE / MAE_o'quv(seasonal naive, 1 qadam)
        birliksiz; qatorlar orasida solishtirish mumkin
        < 1 - o'quvdagi bir qadamli seasonal naive dan yaxshi

MASE — vaqt qatori uchun eng xavfsiz umumiy metrika: nolga bo'lish yo'q (maxraj — o'quv qatorining tebranishi), qatorlar birligiga bog'liq emas, "1" ning ma'nosi aniq. Lekin maxraj bir qadamli xato: 14 kunlik ufqda MASE 1 dan kattaroq chiqishi tabiiy — solishtirishni boshqa modellar bilan qiling, "1" chegarasi bilan emas.

MAPE tuzog'i. Sekin sotiladigan mahsulotda (kuniga 0-5 dona) nol kunlar bor — MAPE aniqlanmagan (sklearn nolga bo'lishni kichik eps bilan almashtiradi va 1e+14 kabi ulkan son beradi). Nolsiz kunlarda ham MAPE |y - f| / y — kichik y li kunlarga katta vazn beradi, shuning uchun optimal doimiy prognoz medianadan past bo'ladi: MAPE bo'yicha tanlangan model sistematik ravishda kam bashorat qiladi — omborda mahsulot yetishmaydi. 4-misolda o'lchaymiz.

2.9. Bazaviylar — har doim birinchi

text
NAIVE:            f_{T+h} = y_T                      "ertaga bugungidek"
SEASONAL NAIVE:   f_{T+h} = y_{T+h-m*k}  (k - eng kichik butun, h <= m*k)
                                                     "o'tgan haftaning shu kuni"
O'RTACHA:         f_{T+h} = mean(y_1..y_T)           "odatdagidek"
DRIFT:            f_{T+h} = y_T + h * (y_T - y_1) / (T - 1)
                                                     "birinchi va oxirgi nuqtadan chiziq"

Nega ular muhim:

  1. Pastki chegara. Model seasonal naive dan yaxshi bo'lmasa, uni ishga tushirishdan ma'no yo'q — ikki qatorli qoida arzonroq, tushunarliroq va buzilmaydi.
  2. Ular kuchli. Kuchli mavsumli, shovqini katta qatorlarda seasonal naive ko'pincha "murakkab" modellarni yengadi. Bashorat musobaqalarida (M-musobaqalari) yillar davomida oddiy usullar va ularning kombinatsiyalari ko'p murakkab usullardan yaxshi natija bergan.
  3. Diagnostika. Qaysi bazaviy yaxshi ishlaydi — qator haqida gapiradi: naive yaxshi — daraja sayr qiladi; seasonal naive yaxshi — mavsum kuchli va barqaror; o'rtacha yaxshi — qator statsionar.

Qaror qoidasi: backtestda eng yaxshi modeldan sezilarli yomon bo'lmagan eng sodda yondashuvni tanlaymiz. Soddalik tartibi oldindan yoziladi.

2.10. Resampling va bo'sh sanalar (qisqa)

3.12-darsda resample va asfreq ni ko'rgan edik. Vaqt qatori modellari uchun ikki qoida muhim:

text
1. MUNTAZAM INDEKS: har kun (soat) uchun bitta qator; freq aniq
   s = s.asfreq("D")                 # yo'q sanalar NaN bo'lib paydo bo'ladi
   s.index.freq                      # statsmodels shuni kutadi

2. AGREGATSIYA: resample - "qanday yig'ish" MA'NOGA bog'liq
   savdo, buyurtmalar  -> .sum()   (hafta savdosi = kunlar yig'indisi)
   harorat, narx, qoldiq -> .mean() yoki .last()
   s.resample("W-SUN").sum();  s.resample("MS").mean()

3. BO'SH SANANI TO'LDIRISH - avval SABABNI aniqlang:
   do'kon YOPIQ edi (savdo haqiqatan 0)      -> 0 bilan
   ma'lumot YOZILMAGAN (tizim xatosi)        -> interpolatsiya /
                                                o'tgan hafta shu kuni / model
   0 va "yozilmagan" ni aralashtirish - trend va mavsumni buzadi

2.11. Tuzoqlar

Asosiy tuzoqlar: vaqt qatorini KFold(shuffle=True) bilan baholash; bazaviysiz "yaxshi natija" e'lon qilish; bir qadamli va ko'p qadamli xatoni aralashtirish; qoplanuvchi test oynalarida SE hisoblash; ko'paytiruvchi qatorni qo'shiluvchi dekompozitsiya qilish; ADF da "p katta" ni "statsionar emas isbotlandi" deb o'qish; KPSS/ADF da c va ct ni farqlamaslik; ortiqcha farqlash; MAPE ni nolli yoki nolga yaqin qatorda ishlatish; bo'sh sanalarni 0 bilan to'ldirish; indeksda freq yo'qligi; daraxt modelidan trendni davom ettirishni kutish; bayram kabi harakatlanuvchi sanalarni yillik mavsum ushlaydi deb o'ylash.


3. Tez ma'lumotnoma

python
import warnings
import numpy as np
import pandas as pd
from statsmodels.tsa.seasonal import MSTL, STL, seasonal_decompose
from statsmodels.tsa.stattools import acf, adfuller, kpss, pacf

s = s.asfreq("D")                                   # muntazam indeks, freq bor
s = s.interpolate("time")                           # yozilmagan kunlar
haftalik = s.resample("W-SUN").sum()                # savdo - yig'indi

dek = seasonal_decompose(np.log(s), model="additive", period=7)
stl = STL(np.log(s), period=7, robust=True).fit()   # .trend .seasonal .resid
mstl = MSTL(np.log(s), periods=(7, 365)).fit()      # ko'p mavsum

with warnings.catch_warnings():
    warnings.simplefilter("ignore")                  # KPSS InterpolationWarning
    p_adf = adfuller(x, regression="c", autolag="AIC")[1]      # H0: birlik ildiz
    p_kpss = kpss(x, regression="c", nlags="auto",
                  result_object=False)[1]                      # H0: statsionar

d1 = s.diff().dropna()                              # trend
d7 = s.diff(7).dropna()                             # haftalik mavsum
r_acf, r_pacf = acf(d7, nlags=21), pacf(d7, nlags=21, method="ywm")
chegara = 1.96 / np.sqrt(len(d7))

# rolling-origin backtest (kengayuvchi oyna)
for o in range(len(y) - k * h, len(y), h):
    f = model(y[:o], h)                             # faqat o'tmish
    xato.append(np.mean(np.abs(y[o:o + h] - f)))

snaive = np.array([y[len(y) - 7 + (i % 7)] for i in range(h)])
mase = mae / np.mean(np.abs(y_oquv[7:] - y_oquv[:-7]))

Qaysi vaziyatda nima

Savol Vosita
Qanday tuzilish bor? grafik + STL/MSTL, trend va mavsum kuchi
Qo'shiluvchimi, ko'paytiruvchimi? mavsum amplitudasi darajaga bog'liqmi → log
Statsionarmi? ADF + KPSS birga, c va ct
Qancha farqlash? eng kam: diff(m) va/yoki diff(1), ACF bilan tekshirish
Xotira tuzilishi? ACF / PACF, +-1.96/sqrt(n)
Model qanchalik yaxshi? rolling-origin backtest, ufq = biznes ufqi
Nimaga nisbatan? naive, seasonal naive, o'rtacha, drift
Qaysi metrika? MAE/RMSE (birlikda), MASE (birliksiz), nollar bo'lsa MAPE emas

Vaqt qatori xulosasi

tartib + avtokorrelyatsiya + ekstrapolyatsiya -> i.i.d. usullar ishlamaydi
dekompozitsiya: log -> STL/MSTL; qoldiqda bayramlar
ADF (H0: birlik ildiz) + KPSS (H0: statsionar) - qarama-qarshi, birga
baholash: faqat o'tmishda o'qitib kelajakda sinash; oynalar bo'yicha SE
bazaviy har doim; eng yaxshisidan sezilarli yomon bo'lmagan eng sodda

4. Batafsil misollar

Misollar real numpy/pandas/statsmodels/sklearn bilan (Python 3.14). Har misol mustaqil ishlaydi.

Misol 1 — Komponentlar: resampling, bo'sh sanalar, dekompozitsiya va STL/MSTL

python
"""Vaqt qatori komponentlari: resample, bo'sh sanalar, klassik dekompozitsiya, STL va MSTL."""

import warnings

import numpy as np
import pandas as pd
from statsmodels.tsa.seasonal import MSTL, STL, seasonal_decompose

# sintetik taqvim: "bayram kunlari" (yillar bo'yicha to'qilgan sanalar)
BAYRAMLAR = pd.to_datetime([
    "2023-03-21", "2024-03-21", "2025-03-21",      # Navro'z
    "2023-04-21", "2024-04-10", "2025-03-30",      # Ramazon hayiti
    "2023-06-28", "2024-06-16", "2025-06-06",      # Qurbon hayiti
    "2023-12-31", "2024-12-31", "2025-12-31",      # Yangi yil arafasi
])
HAFTA = np.log([0.92, 0.90, 0.93, 0.97, 1.06, 1.18, 1.04])   # Du..Ya


def sotuv_yarat(seed=0, kun=1096):
    """Toshkentdagi do'konning kunlik savdosi (ming so'm emas, chek soni)."""
    rng = np.random.default_rng(seed)
    sana = pd.date_range("2023-01-01", periods=kun, freq="D")
    t = np.arange(kun)
    trend = np.log(200) + 0.14 * t / 365.25                     # yiliga ~15%
    hafta = HAFTA[sana.dayofweek] - HAFTA.mean()
    yil = 0.12 * np.sin(2 * np.pi * (sana.dayofyear - 80) / 365.25)
    bayram = np.zeros(kun)
    for b in BAYRAMLAR:
        for oldin, kuch in [(0, 0.30), (1, 0.20), (2, 0.10)]:
            bayram[sana == b - pd.Timedelta(days=oldin)] += kuch
    shovqin = np.zeros(kun)
    e = rng.normal(0, 0.07, kun)
    for i in range(1, kun):
        shovqin[i] = 0.4 * shovqin[i - 1] + e[i]              # AR(1) shovqin
    log_y = trend + hafta + yil + bayram + shovqin
    y = pd.Series(np.exp(log_y), index=sana, name="sotuv")
    qism = pd.DataFrame({"trend": trend, "hafta": hafta, "yil": yil,
                         "bayram": bayram, "shovqin": shovqin}, index=sana)
    return y, qism


def qoldiq_mavsum(qoldiq_nisbiy, yil):
    """Qoldiqda qolgan hafta effekti: hafta kuni o'rtachasining haqiqiy
    hafta effektiga regressiya qiyaligi (0 - qoldiqda mavsum qolmagan)."""
    q = qoldiq_nisbiy[qoldiq_nisbiy.index.year == yil]
    m = q.groupby(q.index.dayofweek).mean().to_numpy()
    h = HAFTA - HAFTA.mean()
    return float(np.sum(m * h) / np.sum(h * h))


def main() -> None:
    y, qism = sotuv_yarat()
    print("=== 1. Ma'lumot va resampling ===")
    print(f"  {len(y)} kun: {y.index[0].date()} - {y.index[-1].date()}, "
          f"freq={y.index.freqstr}")
    oylik = y.resample("MS").mean()
    yillik = y.resample("YS").mean()
    print("  yillik o'rtacha: "
          + ", ".join(f"{d.year}: {v:.1f}" for d, v in yillik.items()))
    print("  2024 oylik o'rtacha (yanvar-dekabr):")
    print("   ", " ".join(f"{v:.0f}" for v in oylik["2024"].to_numpy()))
    kunlar = ["Du", "Se", "Ch", "Pa", "Ju", "Sh", "Ya"]
    hk = y.groupby(y.index.dayofweek).mean() / y.mean()
    print("  hafta kuni / o'rtacha: "
          + " ".join(f"{k}={v:.2f}" for k, v in zip(kunlar, hk.to_numpy())))

    print("\n=== 2. Bo'sh sanalar: asfreq va to'ldirish ===")
    rng = np.random.default_rng(5)
    yoqol = np.sort(rng.choice(np.arange(30, len(y) - 30), 25, replace=False))
    kamchil = y.drop(y.index[yoqol])               # 25 kun umuman yozilmagan
    toliq = kamchil.asfreq("D")                    # sanalar tiklandi, qiymat NaN
    print(f"  qatorlar: {len(kamchil)} -> asfreq dan keyin {len(toliq)}, "
          f"NaN: {int(toliq.isna().sum())}")
    haqiqiy = y.iloc[yoqol].to_numpy()
    usullar = {
        "ffill (oldingi kun)": toliq.ffill(),
        "chiziqli interpolatsiya": toliq.interpolate("time"),
        "o'tgan hafta shu kun": toliq.fillna(toliq.shift(7)),
    }
    for nom, s in usullar.items():
        xato = np.abs(s.iloc[yoqol].to_numpy() - haqiqiy)
        print(f"  {nom:<26} MAE {np.nanmean(xato):6.2f}")
    print("  (0 bilan to'ldirish - 'savdo bo'lmagan' degani; faqat shunday"
          " bo'lsa to'g'ri)")

    print("\n=== 3. Klassik dekompozitsiya: qo'shiluvchi va ko'paytiruvchi ===")
    qosh = seasonal_decompose(y, model="additive", period=7)
    kop = seasonal_decompose(y, model="multiplicative", period=7)
    logd = seasonal_decompose(np.log(y), model="additive", period=7)
    variantlar = {
        "qo'shiluvchi (xom)": qosh.resid / qosh.trend,
        "ko'paytiruvchi (xom)": kop.resid - 1,
        "qo'shiluvchi (log)": logd.resid,
    }
    print(f"  {'variant':<22} {'qiyalik 2023':>13} {'2025':>7}")
    for nom, q in variantlar.items():
        q = q.dropna()
        print(f"  {nom:<22} {qoldiq_mavsum(q, 2023):>+13.3f} "
              f"{qoldiq_mavsum(q, 2025):>+7.3f}")
    amp = qosh.seasonal.iloc[:7]
    print(f"  qo'shiluvchi mavsum amplitudasi (doimiy): "
          f"{amp.max() - amp.min():.1f} chek")
    for yil in [2023, 2025]:
        s = y[str(yil)]
        haq = s.groupby(s.index.dayofweek).mean()
        print(f"  {yil}: haqiqiy hafta ichidagi farq "
              f"{haq.max() - haq.min():.1f} chek")

    print("\n=== 4. STL va MSTL (log qatorda) ===")
    ly = np.log(y)
    stl = STL(ly, period=7, robust=True).fit()
    haq_hafta = qism["hafta"]
    print(f"  STL hafta mavsumi MAE (haqiqiyga nisbatan): "
          f"{np.mean(np.abs(stl.seasonal - haq_hafta)):.4f}")
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        mstl = MSTL(ly, periods=(7, 365)).fit()
    s7, s365 = mstl.seasonal["seasonal_7"], mstl.seasonal["seasonal_365"]
    print(f"  MSTL hafta mavsumi MAE: {np.mean(np.abs(s7 - haq_hafta)):.4f}")
    print(f"  MSTL yillik mavsum MAE: {np.mean(np.abs(s365 - qism['yil'])):.4f}"
          f"  (haqiqiy amplituda {qism['yil'].max() - qism['yil'].min():.3f})")
    osish = (mstl.trend.iloc[-183:].mean() - mstl.trend.iloc[:183].mean()) / (
        (len(y) - 183) / 365.25)
    print(f"  MSTL trend o'sishi: {osish:.3f} log/yil (haqiqiy 0.140)")
    katta = mstl.resid.abs().sort_values(ascending=False).index[:6]
    print("  eng katta qoldiqlar: "
          + ", ".join(sorted(d.strftime("%Y-%m-%d") for d in katta)))
    print(f"  ulardan sintetik taqvimdagi bayram yoki arafa: "
          f"{sum((qism.loc[d, 'bayram'] > 0) for d in katta)} / 6")

    print("\n=== 5. Trend va mavsum kuchi (0 - yo'q, 1 - kuchli) ===")
    r = mstl.resid
    f_t = max(0.0, 1 - r.var() / (mstl.trend + r).var())
    for nom, s in [("hafta", s7), ("yil", s365)]:
        f_s = max(0.0, 1 - r.var() / (s + r).var())
        print(f"  mavsum kuchi ({nom}): {f_s:.3f}")
    print(f"  trend kuchi: {f_t:.3f}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Ma'lumot va resampling ===
  1096 kun: 2023-01-01 - 2025-12-31, freq=D
  yillik o'rtacha: 2023: 217.5, 2024: 251.0, 2025: 285.6
  2024 oylik o'rtacha (yanvar-dekabr):
    204 222 253 252 268 291 279 263 258 243 239 240
  hafta kuni / o'rtacha: Du=0.92 Se=0.90 Ch=0.94 Pa=0.97 Ju=1.06 Sh=1.18 Ya=1.04

=== 2. Bo'sh sanalar: asfreq va to'ldirish ===
  qatorlar: 1071 -> asfreq dan keyin 1096, NaN: 25
  ffill (oldingi kun)        MAE  29.66
  chiziqli interpolatsiya    MAE  14.55
  o'tgan hafta shu kun       MAE  20.62
  (0 bilan to'ldirish - 'savdo bo'lmagan' degani; faqat shunday bo'lsa to'g'ri)

=== 3. Klassik dekompozitsiya: qo'shiluvchi va ko'paytiruvchi ===
  variant                 qiyalik 2023    2025
  qo'shiluvchi (xom)            -0.190  +0.156
  ko'paytiruvchi (xom)          -0.022  +0.043
  qo'shiluvchi (log)            -0.020  +0.040
  qo'shiluvchi mavsum amplitudasi (doimiy): 70.7 chek
  2023: haqiqiy hafta ichidagi farq 58.2 chek
  2025: haqiqiy hafta ichidagi farq 82.4 chek

=== 4. STL va MSTL (log qatorda) ===
  STL hafta mavsumi MAE (haqiqiyga nisbatan): 0.0263
  MSTL hafta mavsumi MAE: 0.0186
  MSTL yillik mavsum MAE: 0.0505  (haqiqiy amplituda 0.240)
  MSTL trend o'sishi: 0.135 log/yil (haqiqiy 0.140)
  eng katta qoldiqlar: 2024-02-19, 2024-04-09, 2024-04-10, 2024-04-23, 2024-06-16, 2024-12-27
  ulardan sintetik taqvimdagi bayram yoki arafa: 3 / 6

=== 5. Trend va mavsum kuchi (0 - yo'q, 1 - kuchli) ===
  mavsum kuchi (hafta): 0.812
  mavsum kuchi (yil): 0.852
  trend kuchi: 0.872

Natija tahlili.

Sintetik do'kon savdosini biz o'zimiz yig'dik: trend (yiliga 0.14 log birlik, ~15%), haftalik mavsum (shanba cho'qqisi), yillik mavsum (yoz cho'qqisi), sintetik taqvimdagi bayram kunlari va arafalari, AR(1) shovqin — hammasi log fazoda qo'shiladi, ya'ni asl fazoda ko'paytiruvchi. Haqiqiy komponentlarni bilganimiz uchun dekompozitsiya qanchalik to'g'ri tiklaganini o'lchay olamiz.

1-bo'lim — resampling. Yillik o'rtacha 217.5 → 251.0 → 285.6: har yili ~15% o'sish. 2024 oylik o'rtachalari yanvardagi 204 dan iyundagi 291 gacha ko'tarilib, dekabrda 240 ga tushadi — yillik mavsum. Hafta kunlari bo'yicha shanba o'rtachadan 18% yuqori (1.18), seshanba 10% past (0.90). resample("MS").mean() va groupby(dayofweek) — dekompozitsiyadan oldingi birinchi, eng arzon qadam.

2-bo'lim — bo'sh sanalar. 25 kun umuman yozilmagan: qator uzunligi 1071, asfreq("D") sanalarni tiklab, ularni NaN qildi. Uch to'ldirish usulining xatosi: ffill — 29.66, chiziqli interpolatsiya — 14.55, "o'tgan hafta shu kuni" — 20.62. Bu qatorda yakka bo'sh kunlar uchun interpolatsiya eng yaxshi: ikki qo'shni kun ham daraja, ham AR shovqin haqida ma'lumot beradi. ffill eng yomoni — u, masalan, jumadagi qiymatni shanbaga ko'chiradi va haftalik mavsumni buzadi. Uzun bo'shliqlarda (bir hafta va undan ko'p) manzara boshqacha bo'ladi — u yerda mavsumni hisobga oladigan usul kerak.

3-bo'lim — qo'shiluvchi va ko'paytiruvchi. Qoldiqda mavsum qolgan-qolmaganini hafta kuni o'rtachalarining haqiqiy hafta effektiga qiyaligi bilan o'lchadik (0 — qoldiqda mavsum yo'q). Xom qatordagi qo'shiluvchi dekompozitsiyada 2023 yilda qiyalik -0.190, 2025 da +0.156: ishorasi teskari. Sababi — qo'shiluvchi model butun qator uchun bitta, chek birligidagi amplituda beradi (70.7), haqiqiy haftalik farq esa 2023 da 58.2, 2025 da 82.4 chek. Boshida mavsum ortiqcha ayiriladi, oxirida yetarlicha ayirilmaydi. Ko'paytiruvchi (-0.022, +0.043) va log fazodagi qo'shiluvchi (-0.020, +0.040) deyarli bir xil va nolga ancha yaqin — ular to'g'ri tuzilish.

4-bo'lim — STL va MSTL log qatorda. STL (faqat haftalik davr) haftalik mavsumni 0.0263 o'rtacha xato bilan tikladi; MSTL (7 va 365) — 0.0186: yillik mavsumni alohida ajratgani uchun haftalik komponent tozaroq. Yillik mavsum xatosi 0.0505 — amplituda 0.240 ga nisbatan kichik, lekin bor: uch yillik ma'lumotda yillik mavsumni ajratish qiyin. Trend o'sishi 0.135 log/yil (haqiqiy 0.140). Eng katta 6 ta qoldiqdan 3 tasi sintetik taqvimdagi bayram yoki arafaga to'g'ri keldi — dekompozitsiya bayramlarni tushuntirmaydi, ularni qoldiqqa tashlaydi. Bu keyingi darslar uchun signal: bayramni alohida belgi yoki tashqi o'zgaruvchi qilish kerak.

5-bo'lim — kuch ko'rsatkichlari: haftalik mavsum 0.812, yillik 0.852, trend 0.872 — uchalasi ham kuchli. Demak, har qanday munosib model uchalasini ham hisobga olishi kerak, bazaviylardan esa seasonal naive (haftalik) eng kuchli nomzod.

Misol 2 — Statsionarlik: ADF va KPSS, farqlash, ACF/PACF

python
"""Statsionarlik: ADF va KPSS, farqlash, ACF/PACF (noldan va statsmodels bilan)."""

import warnings

import numpy as np
import pandas as pd
from statsmodels.tsa.stattools import acf, adfuller, kpss, pacf

HAFTA = np.log([0.92, 0.90, 0.93, 0.97, 1.06, 1.18, 1.04])


def log_sotuv(seed=0, kun=1096):
    """Do'kon savdosining logarifmi: trend + hafta + yil + AR(1) shovqin."""
    rng = np.random.default_rng(seed)
    sana = pd.date_range("2023-01-01", periods=kun, freq="D")
    t = np.arange(kun)
    e = rng.normal(0, 0.07, kun)
    shovqin = np.zeros(kun)
    for i in range(1, kun):
        shovqin[i] = 0.4 * shovqin[i - 1] + e[i]
    ly = (np.log(200) + 0.14 * t / 365.25 + HAFTA[sana.dayofweek]
          + 0.12 * np.sin(2 * np.pi * (sana.dayofyear - 80) / 365.25) + shovqin)
    return pd.Series(ly, index=sana)


def testlar(x, regression="c"):
    """(ADF p, KPSS p). ADF H0: birlik ildiz (statsionar EMAS);
    KPSS H0: statsionar. Nol gipotezalar qarama-qarshi."""
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")          # KPSS jadval chegarasi haqida
        p_adf = adfuller(x, regression=regression, autolag="AIC")[1]
        p_kpss = kpss(x, regression=regression, nlags="auto",
                      result_object=False)[1]
    return p_adf, p_kpss


def xulosa(p_adf, p_kpss, alfa=0.05):
    adf_st = p_adf < alfa                         # ADF H0 rad -> statsionar
    kpss_st = p_kpss >= alfa                      # KPSS H0 rad etilmadi
    if adf_st and kpss_st:
        return "statsionar"
    if not adf_st and not kpss_st:
        return "statsionar EMAS"
    return "ziddiyat - chuqurroq qarang"


def acf_noldan(x, k):
    x = np.asarray(x) - np.mean(x)
    return np.array([np.sum(x[j:] * x[:len(x) - j]) for j in range(k + 1)]) / np.sum(x * x)


def ustun(r, en=20):
    """ASCII ustun: manfiy chapga, musbat o'ngga."""
    n = int(round(abs(r) * en))
    return (" " * (en - n) + "#" * n + "|" + " " * en) if r < 0 else (
        " " * en + "|" + "#" * n + " " * (en - n))


def main() -> None:
    rng = np.random.default_rng(3)
    n = 500
    e = rng.normal(0, 1, n)
    ar = np.zeros(n)
    for i in range(1, n):
        ar[i] = 0.95 * ar[i - 1] + e[i]
    qatorlar = {
        "oq shovqin": e,
        "tasodifiy yurish": np.cumsum(e),
        "trend + shovqin": 0.03 * np.arange(n) + e,
        "AR(1), phi=0.95": ar,
    }
    print("=== 1. ADF va KPSS: nol gipotezalar qarama-qarshi ===")
    print("  ADF  H0: birlik ildiz bor (statsionar EMAS) -> p kichik = statsionar")
    print("  KPSS H0: statsionar                        -> p kichik = statsionar EMAS")
    print(f"  {'qator':<18} {'reg':>3} {'ADF p':>7} {'KPSS p':>7}  xulosa")
    for nom, x in qatorlar.items():
        for reg in ["c", "ct"]:
            pa, pk = testlar(x, reg)
            print(f"  {nom:<18} {reg:>3} {pa:>7.3f} {pk:>7.3f}  {xulosa(pa, pk)}")
    print("  (KPSS p jadvaldan: 0.010 = '<=0.01', 0.100 = '>=0.10')")

    print("\n=== 2. Testlar ham xato qiladi: 100 takror, n=500, reg='c' ===")
    print(f"  {'qator':<18} {'ADF: statsionar':>16} {'KPSS: statsionar':>17}")
    for nom in ["oq shovqin", "AR(1), phi=0.95", "tasodifiy yurish"]:
        adf_ha, kpss_ha = 0, 0
        for s in range(100):
            r = np.random.default_rng(100 + s)
            z = r.normal(0, 1, n)
            if nom == "tasodifiy yurish":
                z = np.cumsum(z)
            elif nom.startswith("AR"):
                for i in range(1, n):
                    z[i] += 0.95 * z[i - 1]
            pa, pk = testlar(z)
            adf_ha += pa < 0.05
            kpss_ha += pk >= 0.05
        print(f"  {nom:<18} {adf_ha / 100:>16.2f} {kpss_ha / 100:>17.2f}")
    print("  to'g'ri javob: 1-2 qator statsionar, 3-qator EMAS")

    print("\n=== 3. Savdo (log): farqlash va mavsumiy farqlash ===")
    ly = log_sotuv()
    variantlar = {
        "log y": ly,
        "diff(1)": ly.diff().dropna(),
        "diff(7)": ly.diff(7).dropna(),
        "diff(7) keyin diff(1)": ly.diff(7).diff().dropna(),
    }
    print(f"  {'variant':<22} {'ADF p':>7} {'KPSS p':>7} {'std':>7}  xulosa")
    for nom, x in variantlar.items():
        pa, pk = testlar(x.to_numpy())
        print(f"  {nom:<22} {pa:>7.3f} {pk:>7.3f} {x.std():>7.4f}  "
              f"{xulosa(pa, pk)}")
    print("  diff(1) keyin ham haftalik mavsum qoladi - ACF 7, 14 da ko'rinadi:")
    a = acf(ly.diff().dropna(), nlags=14)
    print("   ", " ".join(f"{k}:{a[k]:+.2f}" for k in [1, 2, 6, 7, 13, 14]))

    print("\n=== 4. ACF / PACF: AR(1) va MA(1) imzolari (n=500) ===")
    e2 = rng.normal(0, 1, n + 1)
    ar1 = np.zeros(n)
    for i in range(1, n):
        ar1[i] = 0.7 * ar1[i - 1] + e2[i]
    ma1 = e2[1:] + 0.7 * e2[:-1]
    chegara = 1.96 / np.sqrt(n)
    print(f"  95% chegara: +-{chegara:.3f};  noldan ACF == statsmodels: "
          f"{np.allclose(acf_noldan(ar1, 10), acf(ar1, nlags=10))}")
    for nom, x in [("AR(1) phi=0.7", ar1), ("MA(1) theta=0.7", ma1)]:
        a, p = acf(x, nlags=6), pacf(x, nlags=6, method="ywm")
        print(f"  {nom}:  lag   ACF {'':<22}   PACF")
        for k in range(1, 7):
            belgi = lambda v: "*" if abs(v) > chegara else " "
            print(f"    {k:>3} {a[k]:+.2f}{belgi(a[k])} {ustun(a[k], 10)} "
                  f"{p[k]:+.2f}{belgi(p[k])} {ustun(p[k], 10)}")
    print("  AR(p): ACF sekin so'nadi, PACF p dan keyin uziladi")
    print("  MA(q): ACF q dan keyin uziladi, PACF sekin so'nadi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. ADF va KPSS: nol gipotezalar qarama-qarshi ===
  ADF  H0: birlik ildiz bor (statsionar EMAS) -> p kichik = statsionar
  KPSS H0: statsionar                        -> p kichik = statsionar EMAS
  qator              reg   ADF p  KPSS p  xulosa
  oq shovqin           c   0.000   0.100  statsionar
  oq shovqin          ct   0.000   0.100  statsionar
  tasodifiy yurish     c   0.918   0.010  statsionar EMAS
  tasodifiy yurish    ct   0.122   0.035  statsionar EMAS
  trend + shovqin      c   0.948   0.010  statsionar EMAS
  trend + shovqin     ct   0.000   0.100  statsionar
  AR(1), phi=0.95      c   0.006   0.092  statsionar
  AR(1), phi=0.95     ct   0.018   0.100  statsionar
  (KPSS p jadvaldan: 0.010 = '<=0.01', 0.100 = '>=0.10')

=== 2. Testlar ham xato qiladi: 100 takror, n=500, reg='c' ===
  qator               ADF: statsionar  KPSS: statsionar
  oq shovqin                     1.00              0.92
  AR(1), phi=0.95                0.95              0.53
  tasodifiy yurish               0.05              0.03
  to'g'ri javob: 1-2 qator statsionar, 3-qator EMAS

=== 3. Savdo (log): farqlash va mavsumiy farqlash ===
  variant                  ADF p  KPSS p     std  xulosa
  log y                    0.270   0.010  0.1826  statsionar EMAS
  diff(1)                  0.000   0.100  0.1213  statsionar
  diff(7)                  0.000   0.100  0.1052  statsionar
  diff(7) keyin diff(1)    0.000   0.100  0.1133  statsionar
  diff(1) keyin ham haftalik mavsum qoladi - ACF 7, 14 da ko'rinadi:
    1:+0.03 2:-0.25 6:+0.17 7:+0.56 13:+0.16 14:+0.51

=== 4. ACF / PACF: AR(1) va MA(1) imzolari (n=500) ===
  95% chegara: +-0.088;  noldan ACF == statsmodels: True
  AR(1) phi=0.7:  lag   ACF                          PACF
      1 +0.68*           |#######    +0.68*           |#######
      2 +0.46*           |#####      -0.02            |
      3 +0.29*           |###        -0.02            |
      4 +0.19*           |##         -0.01            |
      5 +0.10*           |#          -0.04            |
      6 +0.00            |           -0.08           #|
  MA(1) theta=0.7:  lag   ACF                          PACF
      1 +0.47*           |#####      +0.47*           |#####
      2 -0.02            |           -0.30*        ###|
      3 -0.03            |           +0.18*           |##
      4 +0.00            |           -0.11*          #|
      5 -0.02            |           +0.03            |
      6 -0.07           #|           -0.09*          #|
  AR(p): ACF sekin so'nadi, PACF p dan keyin uziladi
  MA(q): ACF q dan keyin uziladi, PACF sekin so'nadi

Natija tahlili.

1-bo'lim — to'rt qator, ikki test, ikki variant. Oq shovqinda ikkala test rozi: statsionar (ADF p 0.000, KPSS p 0.100 ya'ni >= 0.10). Tasodifiy yurishda ham rozi: statsionar EMAS (ADF p 0.918, KPSS p 0.010). Eng ibratli qator — "trend + shovqin": c variantida ikkala test "statsionar emas" deydi (ADF p 0.948), ct variantida esa ikkalasi "statsionar" (ADF p 0.000, KPSS p 0.100). Qator o'rtachasi o'zgarmas atrofida statsionar emas, lekin chiziqli trend atrofida statsionar — bu trend-statsionar qator, uni farqlash emas, trendni ayirish davolaydi. Test varianti savolni o'zgartiradi, shuning uchun uni ongli tanlang.

2-bo'lim — testlar ham xato qiladi (100 takror, har birida yangi qator). Oq shovqinda ADF 100% to'g'ri, KPSS 92% to'g'ri (qolgan 8% — birinchi tur xato, taxminan belgilangan 5% atrofida). AR(1) phi = 0.95 — statsionar, lekin birlik ildizga juda yaqin: ADF uni 95% hollarda to'g'ri topdi, KPSS esa faqat 53% hollarda "statsionar" dedi — qolganida birlik ildizga yaqin sekin tebranishni nostatsionarlik deb qabul qildi. Tasodifiy yurishda ADF 5% hollarda xato qilib "statsionar" dedi (bu uning nominal alfa si), KPSS — 3%. Xulosa: bitta test bitta qatorda — dalil, isbot emas; ikki test rozi bo'lmasa, chuqurroq qarang.

3-bo'lim — savdo logarifmi. log y statsionar emas (ADF p 0.270, KPSS p 0.010). diff(1) va diff(7) ikkalasi ham testlardan o'tdi. Lekin testlar hammasini ko'rmaydi: diff(1) dan keyin ACF 7-lagda +0.56, 14-lagda +0.51 — haftalik mavsum qolgan, 2-lagda esa -0.25. Test "statsionar" dedi, chunki mavsumiy tebranish o'rtacha atrofida barqaror — lekin model uchun bu tuzilish hali olib tashlanmagan. diff(7) ning std si eng kichik (0.1052), ustiga yana diff(1) qo'shish std ni 0.1133 ga oshirdi — ortiqcha farqlash belgisi.

4-bo'lim — imzolar. ACF noldan yozilgan formula statsmodels bilan mos (True). AR(1) phi = 0.7: ACF sekin so'nadi (0.68, 0.46, 0.29, 0.19, 0.10 — nazariy 0.7^k ga yaqin), PACF birinchi lagdan keyin chegara ichida (-0.02, -0.02, …) — "PACF p dan keyin uziladi". MA(1) theta = 0.7: ACF birinchi lagda 0.47 (nazariy 0.7 / (1 + 0.49) = 0.47), keyin nolga tushadi, PACF esa ishorasini almashtirib so'nadi (-0.30, +0.18, -0.11). E'tibor bering: MA qatorida 6-lagda PACF -0.09 ham yulduzcha oldi — 0.088 chegarasidan arang o'tdi; 12 ta lag tekshirilganda bitta tasodifiy "sezilarli" lag kutilgan hodisa.

Misol 3 — Tasodifiy bo'lish sizishi va rolling-origin backtest

python
"""Baholash: tasodifiy bo'lish sizishi, vaqt bo'yicha bo'lish, rolling-origin backtest."""

import numpy as np
import pandas as pd
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.model_selection import KFold, TimeSeriesSplit

HAFTA = np.log([0.92, 0.90, 0.93, 0.97, 1.06, 1.18, 1.04])


def sotuv_yarat(seed=0, kun=1096):
    rng = np.random.default_rng(seed)
    sana = pd.date_range("2023-01-01", periods=kun, freq="D")
    t = np.arange(kun)
    e = rng.normal(0, 0.07, kun)
    shovqin = np.zeros(kun)
    for i in range(1, kun):
        shovqin[i] = 0.4 * shovqin[i - 1] + e[i]
    daraja = np.cumsum(rng.normal(0, 0.005, kun))   # sekin sayr qiluvchi daraja
    ly = (np.log(200) + 0.14 * t / 365.25 + daraja + HAFTA[sana.dayofweek]
          + 0.12 * np.sin(2 * np.pi * (sana.dayofyear - 80) / 365.25) + shovqin)
    return pd.Series(np.exp(ly), index=sana, name="sotuv")


def belgilar(sana):
    return np.column_stack([np.arange(len(sana)), sana.dayofweek,
                            sana.dayofyear])


def hgb():
    return HistGradientBoostingRegressor(max_iter=60, learning_rate=0.15,
                                         random_state=0)


def cv_mae(model_fn, X, y, bolish):
    xatolar = []
    for tr, te in bolish.split(X):
        m = model_fn().fit(X[tr], y[tr])
        xatolar.append(np.mean(np.abs(m.predict(X[te]) - y[te])))
    return float(np.mean(xatolar))


def main() -> None:
    print("=== 1. Tasodifiy bo'lish sizishi: CV bahosi va haqiqiy kelajak ===")
    print("  model: HistGB (kun raqami, hafta kuni, yil kuni); MAE, chek")
    print("  o'quv: dastlabki 1005 kun, kelajak: oxirgi 91 kun")
    print(f"  {'do_kon':<11} {'KFold aralash':>13} {'TimeSeries':>11} "
          f"{'kelajak':>8}")
    natija = []
    for seed, nom in enumerate(["Chilonzor", "Yunusobod", "Sergeli"]):
        s = sotuv_yarat(seed)
        X, y = belgilar(s.index), s.to_numpy()
        kesim = len(y) - 91                        # oxirgi 91 kun - "kelajak"
        a = cv_mae(hgb, X[:kesim], y[:kesim],
                   KFold(5, shuffle=True, random_state=0))
        b = cv_mae(hgb, X[:kesim], y[:kesim], TimeSeriesSplit(4, test_size=91))
        m = hgb().fit(X[:kesim], y[:kesim])
        k = float(np.mean(np.abs(m.predict(X[kesim:]) - y[kesim:])))
        natija.append((a, b, k))
        print(f"  {nom:<11} {a:>13.2f} {b:>11.2f} {k:>8.2f}")
    a, b, k = np.mean(natija, axis=0)
    print(f"  {'o_rtacha':<11} {a:>13.2f} {b:>11.2f} {k:>8.2f}")
    print(f"  aralash KFold kelajak xatosini {k / a:.2f} marta kichik ko'rsatdi;"
          f" vaqt bo'yicha CV: {k / b:.2f}")
    s = sotuv_yarat(0)                             # keyingi bo'limlar: Chilonzor
    X, y = belgilar(s.index), s.to_numpy()

    print("\n=== 2. Rolling-origin: kengayuvchi va sirpanuvchi oyna ===")
    ufq, qadam, n_oyna, sirp = 28, 28, 10, 365
    boshlar = [len(y) - ufq - qadam * i for i in range(n_oyna)][::-1]
    for nom, oyna in [("kengayuvchi", None), ("sirpanuvchi", sirp)]:
        print(f"  {nom}:")
        for o in [boshlar[0], boshlar[1], boshlar[-1]]:
            bosh = 0 if oyna is None else o - oyna
            print(f"    o'quv {s.index[bosh].date()}..{s.index[o - 1].date()} "
                  f"({o - bosh:>4} kun) -> test {s.index[o].date()}"
                  f"..{s.index[o + ufq - 1].date()}")
    print(f"  jami {n_oyna} oyna, har biri {ufq} kunlik ufq")

    print("\n=== 3. Backtest: seasonal naive va HistGB, ikki oyna turi ===")
    xato = {}
    for o in boshlar:
        test = slice(o, o + ufq)
        snaive = np.tile(y[o - 7:o], ufq // 7)       # oxirgi hafta takrori
        xato.setdefault("seasonal naive", []).append(
            np.mean(np.abs(snaive - y[test])))
        for nom, oyna in [("HistGB kengayuvchi", None),
                          ("HistGB sirpanuvchi", sirp)]:
            bosh = 0 if oyna is None else o - oyna
            m = hgb().fit(X[bosh:o], y[bosh:o])
            xato.setdefault(nom, []).append(
                np.mean(np.abs(m.predict(X[test]) - y[test])))
    xato = {k: np.array(v) for k, v in xato.items()}
    for nom, v in xato.items():
        print(f"  {nom:<20} MAE o'rtacha {v.mean():6.2f}  "
              f"(oynalar: min {v.min():.1f}, max {v.max():.1f})")
    print("  juftlashgan farq (qator - seasonal naive), SE oynalar bo'yicha:")
    for nom in ["HistGB kengayuvchi", "HistGB sirpanuvchi"]:
        d = xato[nom] - xato["seasonal naive"]
        se = d.std(ddof=1) / np.sqrt(len(d))
        hukm = ("sezilarli yomon" if d.mean() > 2 * se else
                "sezilarli yaxshi" if d.mean() < -2 * se else "farq sezilarli emas")
        print(f"    {nom:<20} {d.mean():+6.2f}  SE {se:.2f}  -> {hukm}")
    d = xato["HistGB kengayuvchi"] - xato["HistGB sirpanuvchi"]
    se = d.std(ddof=1) / np.sqrt(len(d))
    print(f"    kengayuvchi - sirpanuvchi: {d.mean():+.2f}  SE {se:.2f}  -> "
          f"{'sezilarli' if abs(d.mean()) > 2 * se else 'farq sezilarli emas'}")

    print("\n=== 4. Daraxt trendni davom ettira oladimi? ===")
    o = boshlar[-1]
    m = hgb().fit(X[:o], y[:o])
    Xk = X[o:o + 7].copy()
    Xk2 = Xk.copy()
    Xk2[:, 0] += 365                               # bir yil keyingi kun raqami
    p1, p2 = m.predict(Xk), m.predict(Xk2)
    print(f"  o'quvdagi max kun raqami {o - 1}; test haftasi t={o}..{o + 6}")
    print(f"  shu hafta kunlari, t va t+365: prognoz o'rtachasi "
          f"{p1.mean():.1f} va {p2.mean():.1f}")
    print(f"  bir xilmi: {np.allclose(p1, p2)} - daraxt o'quv chegarasidan "
          f"tashqarida o'zgarmas")

if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Tasodifiy bo'lish sizishi: CV bahosi va haqiqiy kelajak ===
  model: HistGB (kun raqami, hafta kuni, yil kuni); MAE, chek
  o'quv: dastlabki 1005 kun, kelajak: oxirgi 91 kun
  do_kon      KFold aralash  TimeSeries  kelajak
  Chilonzor           15.72       25.36    19.44
  Yunusobod           16.77       33.30    32.73
  Sergeli             12.30       15.36    15.23
  o_rtacha            14.93       24.67    22.47
  aralash KFold kelajak xatosini 1.51 marta kichik ko'rsatdi; vaqt bo'yicha CV: 0.91

=== 2. Rolling-origin: kengayuvchi va sirpanuvchi oyna ===
  kengayuvchi:
    o'quv 2023-01-01..2025-03-26 ( 816 kun) -> test 2025-03-27..2025-04-23
    o'quv 2023-01-01..2025-04-23 ( 844 kun) -> test 2025-04-24..2025-05-21
    o'quv 2023-01-01..2025-12-03 (1068 kun) -> test 2025-12-04..2025-12-31
  sirpanuvchi:
    o'quv 2024-03-27..2025-03-26 ( 365 kun) -> test 2025-03-27..2025-04-23
    o'quv 2024-04-24..2025-04-23 ( 365 kun) -> test 2025-04-24..2025-05-21
    o'quv 2024-12-04..2025-12-03 ( 365 kun) -> test 2025-12-04..2025-12-31
  jami 10 oyna, har biri 28 kunlik ufq

=== 3. Backtest: seasonal naive va HistGB, ikki oyna turi ===
  seasonal naive       MAE o'rtacha  23.72  (oynalar: min 15.5, max 37.1)
  HistGB kengayuvchi   MAE o'rtacha  18.07  (oynalar: min 12.9, max 22.0)
  HistGB sirpanuvchi   MAE o'rtacha  18.94  (oynalar: min 13.5, max 21.9)
  juftlashgan farq (qator - seasonal naive), SE oynalar bo'yicha:
    HistGB kengayuvchi    -5.65  SE 2.17  -> sezilarli yaxshi
    HistGB sirpanuvchi    -4.78  SE 2.17  -> sezilarli yaxshi
    kengayuvchi - sirpanuvchi: -0.87  SE 0.38  -> sezilarli

=== 4. Daraxt trendni davom ettira oladimi? ===
  o'quvdagi max kun raqami 1067; test haftasi t=1068..1074
  shu hafta kunlari, t va t+365: prognoz o'rtachasi 243.5 va 243.5
  bir xilmi: True - daraxt o'quv chegarasidan tashqarida o'zgarmas

Natija tahlili.

Bu misolda generatorga yana bir real xususiyat qo'shildi — sekin sayr qiluvchi daraja (raqobatchi ochildi, yo'l ta'mirlandi, yangi mahalla qurildi): kichik tasodifiy qadamlar yig'indisi. Model — kalendar belgilari bilan HistGB (kun raqami, hafta kuni, yil kuni); lag yo'q, shuning uchun istalgan ufqqa bashorat bera oladi.

1-bo'lim — asosiy natija. Uch do'konda aralash KFold o'rtacha 14.93 chek xato va'da qildi, oxirgi 91 kundagi haqiqiy xato esa 22.47 — 1.51 marta katta. Vaqt bo'yicha CV (TimeSeriesSplit, 4 ta 91 kunlik oyna) 24.67 berdi — haqiqatdan biroz pessimistik (0.91): uning birinchi oynalarida o'quv ma'lumoti kamroq (yillik mavsumni o'rganish uchun bir yarim yildan kam). Bu to'g'ri tomonga xato: rejalashtirishda kutilganidan yaxshiroq natija yoqimli syurpriz, teskarisi — bo'sh ombor. Do'konlar orasidagi farqqa ham qarang: Yunusobodda kelajak xatosi 32.73, Sergelida 15.23 — bitta 91 kunlik oyna juda shovqinli baho beradi, shuning uchun keyingi bo'limlarda ko'p oynali backtest ishlatamiz. Aralash KFold esa uchala do'konda ham kelajak xatosidan past chiqdi.

2-bo'lim — oynalar sxemasi. Kengayuvchi oynada o'quv 816 kundan 1068 kungacha o'sadi, sirpanuvchida doim 365 kun. Har oyna 28 kunlik ufq, 10 ta oyna qoplanmaydi (qadam = ufq).

3-bo'lim — backtest. Seasonal naive o'rtacha 23.72, HistGB kengayuvchi oynada 18.07, sirpanuvchida 18.94. Juftlashgan farqlar: HistGB seasonal naive dan -5.65 (SE 2.17) va -4.78 (SE 2.17) — ikkalasi ham 2*SE dan katta, sezilarli yaxshi. Oyna turlari orasidagi farq kichik (-0.87), lekin juda barqaror (SE 0.38) — kengayuvchi oyna sezilarli yaxshi: bu qatorda eski ma'lumot yillik mavsumni aniqroq o'rganishga yordam beradi. Farqning barqarorligi juftlashgan taqqoslashning kuchi: ikki model bir xil oynalarda bir xil qiyinchiliklarga duch keladi, shuning uchun ularning farqi o'zlaridan ancha kam tebranadi. Seasonal naive bu yerda yutqazdi, chunki u o'tgan haftani shovqini bilan birga 4 hafta takrorlaydi; HistGB esa ko'p haftalardan o'rtachalangan hafta profilini beradi. Oynalar bo'yicha xato oralig'iga qarang: seasonal naive da 15.5 dan 37.1 gacha — bitta oynaga qarab xulosa qilish xavfli.

4-bo'lim — daraxt modellari trendni davom ettira olmaydi. O'quvdagi eng katta kun raqami 1067; test haftasi uchun t va t + 365 bilan prognoz bir xil (243.5): o'quv chegarasidan tashqarida daraxt oxirgi bargdagi qiymatni beradi. Qisqa ufqda (28 kun) bu kam zarar qiladi, lekin bir yillik ufqda 15% o'sish butunlay yo'qoladi. 28.3-darsda bu muammoni maqsadni o'zgartirish (farq yoki nisbat) va lag belgilar bilan hal qilamiz.

Misol 4 — Metrikalar va bazaviylar: kim kimni yengadi

python
"""Metrikalar (MAE, RMSE, MAPE, sMAPE, MASE) va bazaviy bashoratlar backtesti."""

import numpy as np
import pandas as pd
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, mean_absolute_percentage_error

HAFTA = np.log([0.92, 0.90, 0.93, 0.97, 1.06, 1.18, 1.04])


def sotuv_yarat(seed=0, kun=1096):
    rng = np.random.default_rng(seed)
    sana = pd.date_range("2023-01-01", periods=kun, freq="D")
    t = np.arange(kun)
    e = rng.normal(0, 0.07, kun)
    shovqin = np.zeros(kun)
    for i in range(1, kun):
        shovqin[i] = 0.4 * shovqin[i - 1] + e[i]
    daraja = np.cumsum(rng.normal(0, 0.005, kun))
    ly = (np.log(200) + 0.14 * t / 365.25 + daraja + HAFTA[sana.dayofweek]
          + 0.12 * np.sin(2 * np.pi * (sana.dayofyear - 80) / 365.25) + shovqin)
    return pd.Series(np.exp(ly), index=sana, name="sotuv")


def mae(y, f):
    return float(np.mean(np.abs(y - f)))


def rmse(y, f):
    return float(np.sqrt(np.mean((y - f) ** 2)))


def mape(y, f):
    return float(np.mean(np.abs(y - f) / np.abs(y)) * 100)


def smape(y, f):
    maxraj = (np.abs(y) + np.abs(f)) / 2
    with np.errstate(divide="ignore", invalid="ignore"):
        q = np.where(maxraj == 0, 0.0, np.abs(y - f) / maxraj)
    return float(np.mean(q) * 100)


def mase(y, f, y_oquv, m=7):
    """MAE / (o'quv qatoridagi seasonal naive ning bir qadamli MAE si)."""
    shkala = np.mean(np.abs(y_oquv[m:] - y_oquv[:-m]))
    return mae(y, f) / shkala


# --- bashorat usullari: (o'quv qatori, ufq, kalendar) -> prognoz ---
def naive(y, h, _):
    return np.full(h, y[-1])


def ortacha(y, h, _):
    return np.full(h, y.mean())


def drift(y, h, _):
    qiyalik = (y[-1] - y[0]) / (len(y) - 1)
    return y[-1] + qiyalik * np.arange(1, h + 1)


def snaive(y, h, _, m=7):
    return np.array([y[len(y) - m + (i % m)] for i in range(h)])


def regressiya(daraja):
    def f(y, h, kal):
        t = np.arange(len(y) + h) / 1000
        hafta = np.eye(7)[kal[:len(y) + h]]
        X = np.column_stack([t ** k for k in range(1, daraja + 1)] + [hafta])
        m = LinearRegression().fit(X[:len(y)], np.log(y))
        return np.exp(m.predict(X[len(y):]))
    return f


def histgb(y, h, kal):
    t = np.arange(len(y) + h)
    X = np.column_stack([t, kal[:len(y) + h], (t % 365)])
    m = HistGradientBoostingRegressor(max_iter=100, learning_rate=0.1,
                                      random_state=0).fit(X[:len(y)], y)
    return m.predict(X[len(y):])


def main() -> None:
    rng = np.random.default_rng(4)
    print("=== 1. Metrikalar noldan (sklearn bilan tekshiruv) ===")
    y = rng.uniform(80, 120, 50)
    f = y + rng.normal(0, 8, 50)
    print(f"  MAE  {mae(y, f):.4f}  sklearn {mean_absolute_error(y, f):.4f}")
    print(f"  MAPE {mape(y, f):.4f}% sklearn "
          f"{100 * mean_absolute_percentage_error(y, f):.4f}%")
    print(f"  RMSE {rmse(y, f):.4f} (>= MAE har doim)")

    print("\n=== 2. MAPE tuzog'i: sekin sotiladigan mahsulot ===")
    kam = rng.poisson(3.0, 365).astype(float)       # kuniga 0-9 dona
    print(f"  nol kunlar ulushi: {np.mean(kam == 0):.3f}, o'rtacha "
          f"{kam.mean():.2f}, mediana {np.median(kam):.0f}")
    print(f"  sklearn MAPE (nollar bilan): "
          f"{mean_absolute_percentage_error(kam, np.full(365, 3.0)):.3e}"
          f"  <- nolga bo'lish, eps tufayli ulkan")
    nol_emas = kam > 0
    metrik = {
        "MAE": lambda p: mae(kam, p),
        "MAPE(y>0)": lambda p: mape(kam[nol_emas], p[nol_emas]),
        "sMAPE": lambda p: smape(kam, p),
        "MASE": lambda p: mase(kam, p, kam),
    }
    print(f"  {'doimiy prognoz':>15} " + " ".join(f"{k:>10}" for k in metrik))
    for c in [1.0, 2.0, 3.0, 4.0]:
        p = np.full(365, c)
        print(f"  {c:>15.1f} " + " ".join(f"{f(p):>10.3f}" for f in metrik.values()))
    setka = np.arange(0.0, 6.01, 0.25)
    print("  har metrika bo'yicha eng yaxshi doimiy prognoz (0..6, qadam 0.25):")
    eng = {k: float(setka[np.argmin([f(np.full(365, c)) for c in setka])])
           for k, f in metrik.items()}
    print("   ", ", ".join(f"{k}: {v:.2f}" for k, v in eng.items()))
    if eng["MAPE(y>0)"] < eng["MAE"]:
        print(f"  MAPE {eng['MAPE(y>0)']:.2f} ni tanladi, MAE esa medianani "
              f"({eng['MAE']:.2f}): MAPE past prognozni afzal ko'radi")

    print("\n=== 3. Bazaviylar va 'murakkab' modellar: rolling-origin ===")
    s = sotuv_yarat(0)
    y, kal = s.to_numpy(), s.index.dayofweek.to_numpy()
    ufq, n_oyna = 14, 20
    boshlar = [len(y) - ufq * (i + 1) for i in range(n_oyna)][::-1]
    usullar = {                                   # soddalik tartibida
        "naive": naive, "o'rtacha": ortacha, "seasonal naive": snaive,
        "drift": drift, "trend+hafta (chiziqli)": regressiya(1),
        "trend^5+hafta (polinom)": regressiya(5), "HistGB kalendar": histgb,
    }
    natija = {nom: {"mae": [], "rmse": [], "smape": [], "mase": []}
              for nom in usullar}
    for o in boshlar:
        tr, te = y[:o], y[o:o + ufq]
        for nom, fn in usullar.items():
            p = fn(tr, ufq, kal)
            natija[nom]["mae"].append(mae(te, p))
            natija[nom]["rmse"].append(rmse(te, p))
            natija[nom]["smape"].append(smape(te, p))
            natija[nom]["mase"].append(mase(te, p, tr))
    print(f"  {n_oyna} oyna x {ufq} kun; o'rtacha qiymatlar")
    print(f"  {'usul':<24} {'MAE':>7} {'RMSE':>7} {'sMAPE':>7} {'MASE':>6}")
    for nom, r in natija.items():
        print(f"  {nom:<24} {np.mean(r['mae']):>7.2f} {np.mean(r['rmse']):>7.2f}"
              f" {np.mean(r['smape']):>6.2f}% {np.mean(r['mase']):>6.3f}")

    print("\n=== 4. Qaror: juftlashgan farq (MAE) eng yaxshisiga nisbatan ===")
    eng = min(natija, key=lambda k: np.mean(natija[k]["mae"]))
    print(f"  eng past MAE: {eng}")
    munosib = []
    for nom in usullar:
        if nom == eng:
            continue
        d = np.array(natija[nom]["mae"]) - np.array(natija[eng]["mae"])
        se = d.std(ddof=1) / np.sqrt(len(d))
        yomon = d.mean() > 2 * se
        if not yomon:
            munosib.append(nom)
        print(f"  {nom:<24} {d.mean():+7.2f}  SE {se:5.2f}  "
              f"{'sezilarli yomon' if yomon else 'farq sezilarli emas'}")
    tanlov = next(n for n in usullar if n == eng or n in munosib)
    print(f"  qoida 'eng yaxshisidan sezilarli yomon bo'lmagan eng sodda': "
          f"{tanlov}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Metrikalar noldan (sklearn bilan tekshiruv) ===
  MAE  6.8264  sklearn 6.8264
  MAPE 6.5985% sklearn 6.5985%
  RMSE 8.3580 (>= MAE har doim)

=== 2. MAPE tuzog'i: sekin sotiladigan mahsulot ===
  nol kunlar ulushi: 0.044, o'rtacha 3.02, mediana 3
  sklearn MAPE (nollar bilan): 5.923e+14  <- nolga bo'lish, eps tufayli ulkan
   doimiy prognoz        MAE  MAPE(y>0)      sMAPE       MASE
              1.0      2.112     57.833     93.903      1.160
              2.0      1.458     42.600     55.160      0.800
              3.0      1.296     53.156     47.475      0.712
              4.0      1.578     79.184     52.995      0.866
  har metrika bo'yicha eng yaxshi doimiy prognoz (0..6, qadam 0.25):
    MAE: 3.00, MAPE(y>0): 2.00, sMAPE: 3.00, MASE: 3.00
  MAPE 2.00 ni tanladi, MAE esa medianani 3.00-bob: MAPE past prognozni afzal ko'radi

=== 3. Bazaviylar va 'murakkab' modellar: rolling-origin ===
  20 oyna x 14 kun; o'rtacha qiymatlar
  usul                         MAE    RMSE   sMAPE   MASE
  naive                      31.28   39.89  11.19%  1.416
  o'rtacha                   32.56   42.34  11.59%  1.477
  seasonal naive             24.28   29.44   8.56%  1.099
  drift                      31.20   39.73  11.16%  1.413
  trend+hafta (chiziqli)     28.55   32.39  10.01%  1.289
  trend^5+hafta (polinom)    52.35   56.46  16.92%  2.374
  HistGB kalendar            19.44   23.49   6.91%  0.880

=== 4. Qaror: juftlashgan farq (MAE) eng yaxshisiga nisbatan ===
  eng past MAE: HistGB kalendar
  naive                     +11.84  SE  1.09  sezilarli yomon
  o'rtacha                  +13.13  SE  1.94  sezilarli yomon
  seasonal naive             +4.84  SE  1.46  sezilarli yomon
  drift                     +11.77  SE  1.10  sezilarli yomon
  trend+hafta (chiziqli)     +9.12  SE  3.47  sezilarli yomon
  trend^5+hafta (polinom)   +32.91  SE  4.91  sezilarli yomon
  qoida 'eng yaxshisidan sezilarli yomon bo'lmagan eng sodda': HistGB kalendar

Natija tahlili.

1-bo'lim — noldan yozilgan MAE va MAPE sklearn bilan to'liq mos; RMSE (8.3580) MAE dan (6.8264) katta — bu har doim shunday (tenglik faqat barcha xatolar bir xil bo'lganda).

2-bo'lim — MAPE tuzog'i. Sekin sotiladigan mahsulotda kunlarning 4.4% ida savdo nol. sklearn MAPE 5.923e+14 — nolga bo'lishni eps bilan almashtirgani uchun ma'nosiz ulkan son. Nolsiz kunlarda ham MAPE boshqa metrikalarga qarshi chiqadi: MAE, sMAPE va MASE eng yaxshi doimiy prognoz sifatida 3.00 ni (mediana) tanladi, MAPE esa 2.00 ni. Ya'ni MAPE bo'yicha "optimallashtirilgan" model har kuni bir dona kam buyurtma beradi. Sababi — |y - f| / y da kichik y li kunlar katta vazn oladi va prognozni pastga tortadi. Bu seed da sMAPE mediana bilan mos keldi, lekin u ham nolga yaqin qiymatlarda beqaror (y = 0 va f > 0 da har doim 200%).

3-bo'lim — 20 ta 14 kunlik oynada yetti usul. Eng yaxshi — HistGB kalendar (MAE 19.44, MASE 0.880). Bazaviylar orasida seasonal naive (24.28) aniq yetakchi: naive (31.28), o'rtacha (32.56) va drift (31.20) haftalik mavsumni bilmaydi. Endi "murakkab" modellarga qarang: trend va hafta kuni bilan chiziqli regressiya (28.55) seasonal naive dan yomon — u yillik mavsumni va sayr qiluvchi darajani bilmaydi; 5-darajali polinom trend (52.35) esa eng yomoni — polinom o'quv chegarasidan tashqarida tez og'ib ketadi. Ikki "ilmiy" model eng oddiy bazaviyga yutqazdi — bu amalda juda ko'p uchraydigan manzara.

4-bo'lim — qaror. Eng past MAE — HistGB. Qolgan barcha usullar undan sezilarli yomon: seasonal naive +4.84 (SE 1.46), chiziqli regressiya +9.12 (SE 3.47) va hokazo. Qoida "eng yaxshisidan sezilarli yomon bo'lmagan eng sodda" bu safar HistGB ni tanladi — chunki undan soddaroq birorta ham usul unga yetib kelmadi. Bu halol natija: bazaviylar har doim yutmaydi, lekin ular bo'lmaganda biz HistGB seasonal naive dan ~20% yaxshi ekanini ham, polinom modelning halokatli ekanini ham bilmas edik. Keyingi darsda seasonal naive ga qarshi klassik modellarni (ETS, SARIMA) xuddi shu tartibda sinaymiz.


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

Noto'g'ri fikr To'g'risi
"Vaqt qatori — belgisi sana bo'lgan oddiy regressiya" Tartib, avtokorrelyatsiya va ekstrapolyatsiya baholashni ham, modelni ham o'zgartiradi
"KFold(shuffle=True) baribir o'rtacha to'g'ri baho beradi" Qo'shni kunlar sizadi; 3-misolda baho 1.51 marta optimistik
"ADF p katta — qator statsionar emasligi isbotlandi" Faqat dalil yetmadi; KPSS bilan birga tekshiring
"Ikkala test ham bir xil savolga javob beradi" Nol gipotezalari qarama-qarshi: ADF — birlik ildiz, KPSS — statsionarlik
"Test 'statsionar' dedi — tuzilish qolmadi" diff(1) dan keyin ham ACF 7-lagda 0.56 — mavsum qolgan
"Ko'proq farqlash — xavfsizroq" Ortiqcha farqlash dispersiyani oshiradi (0.1052 → 0.1133)
"Qo'shiluvchi dekompozitsiya har doim yaroqli" O'sayotgan qatorda mavsum amplitudasi o'sadi — log yoki ko'paytiruvchi
"MAPE — universal, tushunarli metrika" Nolda cheksiz; past prognozni afzal ko'radi
"Murakkab model bazaviydan yaxshi bo'lishi aniq" Polinom va chiziqli regressiya seasonal naive ga yutqazdi
"Bitta test oynasi yetarli" Oynalar orasida xato 2 barobardan ko'p farq qiladi — ko'p oyna va SE
"Daraxt modeli trendni o'rganadi" O'quv chegarasidan tashqarida prognoz o'zgarmaydi

6. Keng tarqalgan xatolar va yechimlari

1. Aralash bo'lish

python
cross_val_score(model, X, y, cv=KFold(5, shuffle=True))            # ⚠️
cross_val_score(model, X, y, cv=TimeSeriesSplit(5, test_size=28))   # ✅

2. Bazaviysiz natija

python
print("MAE:", mae(y_test, model_prognoz))                           # ⚠️ nimaga nisbatan?
d = xato_model - xato_snaive                                        # ✅ oynalar bo'yicha
print(d.mean(), d.std(ddof=1) / np.sqrt(len(d)))

3. Qoplanuvchi test oynalari

python
boshlar = range(700, 1000, 1)       # ufq 28, qadam 1                ⚠️ SE juda kichik
boshlar = range(700, 1000, 28)      # qadam >= ufq                   ✅

4. Ko'paytiruvchi qatorni qo'shiluvchi dekompozitsiya qilish

python
seasonal_decompose(savdo, model="additive", period=7)               # ⚠️
seasonal_decompose(np.log(savdo), model="additive", period=7)       # ✅

5. Faqat bitta statsionarlik testi

python
if adfuller(x)[1] > 0.05: print("statsionar emas")                 # ⚠️
p_adf, p_kpss = adfuller(x)[1], kpss(x, result_object=False)[1]     # ✅ ikkalasi

6. Nol qiymatli qatorda MAPE

python
mean_absolute_percentage_error(y, f)                                # ⚠️ y = 0 bor
np.sum(np.abs(y - f)) / np.sum(np.abs(y))                           # ✅ WAPE yoki MASE

7. Bo'sh sanani 0 bilan to'ldirish

python
s = s.asfreq("D").fillna(0)                                         # ⚠️ yozilmagan != 0
s = s.asfreq("D").interpolate("time")                               # ✅ sababga qarab

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 3.12-dars (o'tilgan): DatetimeIndex, resample, shift — vaqt qatori bilan ishlashning pandas asosi
  • 11.1, 11.9-darslar (o'tilgan): nol gipoteza mantiqi va ko'p testlash — ADF/KPSS va ACF chegaralarini to'g'ri o'qish
  • 12.8-dars (o'tilgan): regressiya metrikalari, MAPE va WAPE
  • 13.4-dars (o'tilgan): qoldiqlar avtokorrelyatsiyasi (Durbin-Watson)
  • 17.5, 17.8-darslar (o'tilgan): sana belgilari, lag va vaqt leakage i
  • 18.2, 18.10-darslar (o'tilgan): TimeSeriesSplit, juftlashgan taqqoslash
  • 27.12-dars (o'tilgan): mavsumiy o'zgarish — drift emas; reference oyna mavsumni qamrashi kerak
  • 28.2-dars: ETS, ARIMA/SARIMA — shu darsdagi dekompozitsiya, farqlash va ACF/PACF ustiga quriladi
  • 28.3-dars: ML bilan bashorat — lag belgilar, global modellar, ko'p qadamli strategiyalar; baholash shu darsdagi backtest

8. Eng yaxshi amaliyotlar

  1. Avval grafik va dekompozitsiya: qanday mavsum, qo'shiluvchimi yoki ko'paytiruvchimi, qoldiqda nima bor.

  2. Muntazam indeks va aniq freq; bo'sh sanani to'ldirishdan oldin sababini aniqlang.

  3. Statsionarlikni ADF va KPSS bilan birga, to'g'ri variantda (c/ct) tekshiring.

  4. Eng kam farqlash; ortiqchasini ACF va std bilan tekshiring.

  5. Faqat vaqt bo'yicha baholang: rolling-origin, ufq biznesga mos, oynalar qoplanmasin.

  6. Har doim bazaviy: kamida seasonal naive; juftlashgan farq va SE bilan.

  7. Metrikani ma'lumotga moslang: nollar bo'lsa MAPE emas — MAE, WAPE, MASE.

  8. Qaror qoidasini oldindan yozing: eng yaxshisidan sezilarli yomon bo'lmagan eng sodda.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # KFold(shuffle=True) vaqt qatorida bahoni qaysi tomonga siljitadi?
2.  # ADF p = 0.40 - qator statsionar emasligi isbotlandimi?
3.  # KPSS ning nol gipotezasi nima?
4.  # oq shovqinni farqlasak, dispersiya qanday o'zgaradi?
5.  # AR(1) da PACF qaysi lagdan keyin uziladi?
6.  # MA(1) da ACF qaysi lagdan keyin uziladi?
7.  # n = 400 bo'lsa, ACF ning 95% chegarasi?
8.  # kunlik savdoda diff(1) haftalik mavsumni olib tashlaydimi?
9.  # y da nollar bor - MAPE?
10. # MASE = 0.8 nimani bildiradi?
11. # daraxt modeli o'quvdan keyingi kunlar uchun trendni davom ettiradimi?
12. # 28 kunlik ufq, test oynalari har kuni siljiydi - SE ga nima bo'ladi?
Javoblar
  1. Optimistik tomonga — xato kichik ko'rinadi (qo'shni kunlar va kelajak ma'lumoti sizadi)
  2. Yo'q — faqat birlik ildizni rad etishga dalil yetmadi; KPSS bilan tekshiring
  3. Qator statsionar
  4. Ikki barobar oshadi: Var(e_t - e_{t-1}) = 2 * sigma^2
  5. 1-lagdan keyin
  6. 1-lagdan keyin
  7. 1.96 / sqrt(400) = 0.098
  8. Yo'q — ACF 7, 14-laglarda cho'qqilar qoladi; diff(7) kerak
  9. Aniqlanmagan (nolga bo'lish); MAE, WAPE yoki MASE ishlating
  10. Model xatosi o'quvdagi bir qadamli seasonal naive xatosining 80% i
  11. Yo'q — o'quv chegarasidan tashqarida prognoz o'zgarmaydi
  12. Oynalar qoplanadi, xatolar korrelyatsiyalanadi — SE sun'iy kichik chiqadi

Vazifa 2: Xatolarni tuzating

python
1.  scores = cross_val_score(HistGradientBoostingRegressor(), X, y,
                             cv=KFold(5, shuffle=True, random_state=0))

2.  dek = seasonal_decompose(savdo, model="additive", period=7)   # savdo 3 yilda 2x o'sgan

3.  if adfuller(x)[1] > 0.05:
        print("statsionar emas - ikki marta farqlaymiz")
        x = x.diff().diff()

4.  s = s.asfreq("D").fillna(0)          # 25 kun tizim xatosi bilan yozilmagan

5.  metrika = mean_absolute_percentage_error(y_kunlik_dona, f)   # kunlarning 20% i nol
Javoblar
python
1.  scores = cross_val_score(HistGradientBoostingRegressor(), X, y,
                             cv=TimeSeriesSplit(5, test_size=28))

2.  dek = seasonal_decompose(np.log(savdo), model="additive", period=7)

3.  # ADF + KPSS birga; kerakli eng kam farqlash, ACF bilan tekshirish
    x1 = x.diff(7).dropna()          # kunlik savdo: avval mavsumiy farq

4.  s = s.asfreq("D").interpolate("time")   # yoki o'tgan hafta shu kuni

5.  wape = np.sum(np.abs(y - f)) / np.sum(np.abs(y))   # yoki MASE

Vazifa 3: Dekompozitsiya

Modellang (1-misol asosida):

  1. Soatlik elektr iste'moli qatorini yarating (kunlik davr 24, haftalik 168) va MSTL(periods=(24, 168)) bilan ikkala mavsumni tiklang
  2. Yillik mavsum amplitudasini yildan-yilga o'stiring (ko'paytiruvchi emas, "evolyutsiya") va STL ning seasonal parametri (7, 13, 35) ta'sirini o'lchang
  3. Bo'sh kunlarni ketma-ket 7 kunlik blok qilib, uchta to'ldirish usulini qayta solishtiring
  4. Bayram kunlarini STL(robust=True) va robust=False da solishtiring: qaysi biri mavsumni kamroq buzadi?

Vazifa 4: Statsionarlik

Modellang (2-misol asosida):

  1. n = 100, 500, 2000 da AR(1) phi = 0.95 uchun ADF va KPSS "to'g'ri javob" ulushini o'lchang
  2. Struktura o'zgarishi: 250-nuqtada o'rtachasi sakraydigan oq shovqin — testlar nima deydi?
  3. Oq shovqinni bir marta va ikki marta farqlab, ACF(1) va std ni chop eting
  4. Savdo diff(7) qatori uchun ACF/PACF jadvalini chiqarib, qaysi lag lar sezilarli ekanini yozing

Vazifa 5: Baholash

Modellang (3-misol asosida):

  1. Do'konlar sonini 10 ga oshirib, "kelajak / KFold" nisbatining o'rtachasi va SE sini hisoblang
  2. HistGB ga lag_7 belgisini qo'shing (faqat 7 kunlik ufq uchun) va aralash KFold sizishi qanday o'zgarishini o'lchang
  3. TimeSeriesSplit(gap=7) bilan natija qanday o'zgaradi?
  4. Sirpanuvchi oyna uzunligini 180, 365, 730 qilib, juftlashgan farqlarni chiqaring

Vazifa 6: Bazaviylar

Modellang (4-misol asosida):

  1. "4 hafta o'rtachasi" bazaviysini qo'shing: f = mean(y_{T+h-7k}, k=1..4) — seasonal naive dan yaxshimi?
  2. Ufqni 7, 14, 28 qilib, har ufqda eng yaxshi usul o'zgaradimi?
  3. Qatorni log ga o'tkazib HistGB ni o'qiting (prognozni exp qiling) — natija o'zgaradimi?
  4. WAPE ni qo'shing va MAE bilan bir xil tartib berishini tekshiring

Vazifa 7: O'ylash

Menejer: "Bizning yangi neyron tarmog'imiz test to'plamda 4% MAPE berdi — eskisi 9% edi. Ertadan ishga tushiramiz." Test to'plam — 2024-2025 yillar kunlarining tasodifiy 20% i. Nima deysiz?

Javob

Qisqa javob: hozircha ishga tushirmang — raqam noto'g'ri savolga javob beryapti. Uch narsani tekshirish kerak.

1. Baholash dizayni. Tasodifiy 20% kun — aralash bo'lish. Har test kunining ikki tomonidagi qo'shnilar o'quvda, kelajak ma'lumoti o'tmishni "bashorat qilishga" yordam beradi. 3-misolda xuddi shunday baho uchta do'konda o'rtacha 1.51 marta optimistik chiqdi. Neyron tarmoq kabi moslashuvchan model qo'shnilarni eslab qolishda yanada kuchli — sizish kattaroq bo'lishi mumkin.

python
# 1) rolling-origin: 2024-01 dan boshlab har 14 kunda origin, ufq 14 (ombor ufqi)
# 2) eski model, yangi model va seasonal naive - BIR XIL oynalarda
# 3) juftlashgan farq + SE oynalar bo'yicha; qoida oldindan yozilgan

2. Metrika. MAPE past prognozni afzal ko'radi (4-misolda MAPE-optimal prognoz medianadan bir dona past). Omborda bu — muntazam tanqislik. Nollar bo'lsa MAPE umuman aniqlanmagan. MAE (chek yoki dona birligida) va MASE ni ham ko'rsating, biznes uchun esa asimmetrik narx (tanqislik va ortiqcha qoldiq narxi) bo'yicha baholang.

3. Bazaviy. 4% va 9% — nimaga nisbatan? Seasonal naive qancha beradi? Agar u 10% bersa va yangi model halol backtestda 9.5% bersa — murakkablikni oqlaydigan yutuq yo'q.

Menejerga javob: "4% — aralash test kunlarida olingan, u kelajakdagi xatoni sezilarli kamaytirib ko'rsatadi. Ikki kun ichida halol backtest qilaman: yangi model, eski model va 'o'tgan hafta' qoidasi bir xil 14 kunlik oynalarda, MAE va MASE bilan. Yangi model eskisidan sezilarli yaxshi bo'lsa — ishga tushiramiz; bo'lmasa, eskisini qoldiramiz va tejalgan vaqtni bayram effektlariga sarflaymiz."

Nimani mustahkamlaydi: 2.4, 2.7, 2.8, 2.9-bo'limlar.


Xulosa

Bu darsda vaqt qatorini tushunish, tekshirish va halol baholashni o'rgandik.

Eng muhim uch fikr:

  1. Vaqt qatori — tuzilishga ega ma'lumot. 1-misolda log fazodagi MSTL trendni (0.135 va haqiqiy 0.140 log/yil), haftalik va yillik mavsumni tikladi; qo'shiluvchi dekompozitsiya esa o'sayotgan qatorda mavsumni boshida ortiqcha, oxirida kam ayirdi (qiyalik -0.190 → +0.156). Bayramlar qoldiqqa tushdi — ularni alohida modellash kerak. 2-misolda ADF va KPSS qarama-qarshi nol gipotezalari bilan birga ishlatildi: "trend + shovqin" c variantida statsionar emas, ct da statsionar chiqdi; diff(1) dan keyin testlar o'tsa ham ACF 7-lagda 0.56 mavsum qoldi, ortiqcha farqlash esa std ni oshirdi.

  2. Baholash — faqat o'tmishda o'qitib, kelajakda. 3-misolda aralash KFold uchta do'konda kelajak xatosini o'rtacha 1.51 marta kichik ko'rsatdi; vaqt bo'yicha CV esa ozgina pessimistik (0.91) edi. Rolling-origin backtest ko'p oyna beradi va juftlashgan farqni SE bilan baholashga imkon beradi; bitta oyna juda shovqinli (do'konlar orasida 15.23 dan 32.73 gacha). Daraxt modeli o'quv chegarasidan tashqarida trendni davom ettirmaydi.

  3. Bazaviysiz raqam — raqam emas. 4-misolda chiziqli regressiya va polinom trend eng oddiy seasonal naive ga yutqazdi (28.55 va 52.35 ga qarshi 24.28), HistGB esa undan sezilarli yaxshi chiqdi (+4.84, SE 1.46) — qoida uni halol tanladi. MAPE nollarda ma'nosiz va past prognozni afzal ko'radi; MAE, WAPE va MASE xavfsizroq.

Keyingi darsda Klassik bashorat modellari: eksponensial silliqlash (SES, Holt, Holt-Winters, ETS), ARIMA va SARIMA, tartibni ACF/PACF va AIC bilan tanlash, qoldiqlar diagnostikasi, bashorat intervallarining haqiqiy qamrovi va rolling-origin backtestda seasonal naive, ETS va SARIMA ning juftlashgan taqqoslashi.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
28.1-dars: Vaqt qatorlari asoslari — IlmHamroh