Mundarija (25)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Vaqt qatori nima va nega u "oddiy jadval" emas
- 2.2. Komponentlar
- 2.3. Dekompozitsiya: klassik va STL
- 2.4. Statsionarlik va ikki test
- 2.5. Farqlash va mavsumiy farqlash
- 2.6. ACF va PACF
- 2.7. Baholash: tasodifiy bo'lish nega xato
- 2.8. Metrikalar
- 2.9. Bazaviylar — har doim birinchi
- 2.10. Resampling va bo'sh sanalar (qisqa)
- 2.11. Tuzoqlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Komponentlar: resampling, bo'sh sanalar, dekompozitsiya va STL/MSTL
- Misol 2 — Statsionarlik: ADF va KPSS, farqlash, ACF/PACF
- Misol 3 — Tasodifiy bo'lish sizishi va rolling-origin backtest
- Misol 4 — Metrikalar va bazaviylar: kim kimni yengadi
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
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:
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 keladiShu 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:
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 avtokorrelyatsiyalanganQo'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:
log y_t = log TREND_t + log MAVSUM_t + log SHOVQIN_tLog 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):
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 mavsumSTL (Seasonal-Trend decomposition using Loess) — mahalliy silliqlash (Loess) bilan ishlaydi:
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):
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 - kuchliDekompozitsiya — 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:
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:
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(indeksdafreqyo'q),FutureWarning(API o'zgarishi). Bu darslarda indeksga har doimfreqberamiz (pd.date_range(..., freq="D")) va ma'lum, tushunilgan ogohlantirishlarniwith warnings.catch_warnings(): warnings.simplefilter("ignore")bilan faqat shu chaqiruv atrofida o'raymiz. Ogohlantirishni butun dastur bo'yicha o'chirmang —ConvergenceWarninghaqiqiy muammoni bildirishi mumkin.
2.5. Farqlash va mavsumiy farqlash
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 tanlangKunlik 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
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'qqilarBu 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:
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'xshaydiNatija — 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:
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 paytDizayn 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()vaSE = 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:
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 yaxshiMASE — 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
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:
- Pastki chegara. Model seasonal naive dan yaxshi bo'lmasa, uni ishga tushirishdan ma'no yo'q — ikki qatorli qoida arzonroq, tushunarliroq va buzilmaydi.
- 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.
- 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:
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 buzadi2.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
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 sodda4. 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
"""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:
=== 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.872Natija 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
"""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:
=== 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'nadiNatija 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
"""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:
=== 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'zgarmasNatija 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
"""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:
=== 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 kalendarNatija 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
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
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
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
seasonal_decompose(savdo, model="additive", period=7) # ⚠️
seasonal_decompose(np.log(savdo), model="additive", period=7) # ✅5. Faqat bitta statsionarlik testi
if adfuller(x)[1] > 0.05: print("statsionar emas") # ⚠️
p_adf, p_kpss = adfuller(x)[1], kpss(x, result_object=False)[1] # ✅ ikkalasi6. Nol qiymatli qatorda MAPE
mean_absolute_percentage_error(y, f) # ⚠️ y = 0 bor
np.sum(np.abs(y - f)) / np.sum(np.abs(y)) # ✅ WAPE yoki MASE7. Bo'sh sanani 0 bilan to'ldirish
s = s.asfreq("D").fillna(0) # ⚠️ yozilmagan != 0
s = s.asfreq("D").interpolate("time") # ✅ sababga qarab7. 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
Avval grafik va dekompozitsiya: qanday mavsum, qo'shiluvchimi yoki ko'paytiruvchimi, qoldiqda nima bor.
Muntazam indeks va aniq
freq; bo'sh sanani to'ldirishdan oldin sababini aniqlang.Statsionarlikni ADF va KPSS bilan birga, to'g'ri variantda (
c/ct) tekshiring.Eng kam farqlash; ortiqchasini ACF va std bilan tekshiring.
Faqat vaqt bo'yicha baholang: rolling-origin, ufq biznesga mos, oynalar qoplanmasin.
Har doim bazaviy: kamida seasonal naive; juftlashgan farq va SE bilan.
Metrikani ma'lumotga moslang: nollar bo'lsa MAPE emas — MAE, WAPE, MASE.
Qaror qoidasini oldindan yozing: eng yaxshisidan sezilarli yomon bo'lmagan eng sodda.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
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
- Optimistik tomonga — xato kichik ko'rinadi (qo'shni kunlar va kelajak ma'lumoti sizadi)
- Yo'q — faqat birlik ildizni rad etishga dalil yetmadi; KPSS bilan tekshiring
- Qator statsionar
- Ikki barobar oshadi:
Var(e_t - e_{t-1}) = 2 * sigma^2 - 1-lagdan keyin
- 1-lagdan keyin
1.96 / sqrt(400) = 0.098- Yo'q — ACF 7, 14-laglarda cho'qqilar qoladi;
diff(7)kerak - Aniqlanmagan (nolga bo'lish); MAE, WAPE yoki MASE ishlating
- Model xatosi o'quvdagi bir qadamli seasonal naive xatosining 80% i
- Yo'q — o'quv chegarasidan tashqarida prognoz o'zgarmaydi
- Oynalar qoplanadi, xatolar korrelyatsiyalanadi — SE sun'iy kichik chiqadi
Vazifa 2: Xatolarni tuzating
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 nolJavoblar
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 MASEVazifa 3: Dekompozitsiya
Modellang (1-misol asosida):
- Soatlik elektr iste'moli qatorini yarating (kunlik davr 24, haftalik 168) va
MSTL(periods=(24, 168))bilan ikkala mavsumni tiklang - Yillik mavsum amplitudasini yildan-yilga o'stiring (ko'paytiruvchi emas, "evolyutsiya") va
STLningseasonalparametri (7, 13, 35) ta'sirini o'lchang - Bo'sh kunlarni ketma-ket 7 kunlik blok qilib, uchta to'ldirish usulini qayta solishtiring
- Bayram kunlarini
STL(robust=True)varobust=Falseda solishtiring: qaysi biri mavsumni kamroq buzadi?
Vazifa 4: Statsionarlik
Modellang (2-misol asosida):
n = 100, 500, 2000da AR(1)phi = 0.95uchun ADF va KPSS "to'g'ri javob" ulushini o'lchang- Struktura o'zgarishi: 250-nuqtada o'rtachasi sakraydigan oq shovqin — testlar nima deydi?
- Oq shovqinni bir marta va ikki marta farqlab, ACF(1) va std ni chop eting
- Savdo
diff(7)qatori uchun ACF/PACF jadvalini chiqarib, qaysi lag lar sezilarli ekanini yozing
Vazifa 5: Baholash
Modellang (3-misol asosida):
- Do'konlar sonini 10 ga oshirib, "kelajak / KFold" nisbatining o'rtachasi va SE sini hisoblang
- HistGB ga
lag_7belgisini qo'shing (faqat 7 kunlik ufq uchun) va aralash KFold sizishi qanday o'zgarishini o'lchang TimeSeriesSplit(gap=7)bilan natija qanday o'zgaradi?- Sirpanuvchi oyna uzunligini 180, 365, 730 qilib, juftlashgan farqlarni chiqaring
Vazifa 6: Bazaviylar
Modellang (4-misol asosida):
- "4 hafta o'rtachasi" bazaviysini qo'shing:
f = mean(y_{T+h-7k}, k=1..4)— seasonal naive dan yaxshimi? - Ufqni 7, 14, 28 qilib, har ufqda eng yaxshi usul o'zgaradimi?
- Qatorni log ga o'tkazib HistGB ni o'qiting (prognozni
expqiling) — natija o'zgaradimi? - 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.
# 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 yozilgan2. 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:
Vaqt qatori — tuzilishga ega ma'lumot. 1-misolda log fazodagi MSTL trendni (
0.135va haqiqiy0.140log/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"cvariantida statsionar emas,ctda statsionar chiqdi;diff(1)dan keyin testlar o'tsa ham ACF 7-lagda0.56mavsum qoldi, ortiqcha farqlash esa std ni oshirdi.Baholash — faqat o'tmishda o'qitib, kelajakda. 3-misolda aralash
KFolduchta do'konda kelajak xatosini o'rtacha1.51marta 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 orasida15.23dan32.73gacha). Daraxt modeli o'quv chegarasidan tashqarida trendni davom ettirmaydi.Bazaviysiz raqam — raqam emas. 4-misolda chiziqli regressiya va polinom trend eng oddiy seasonal naive ga yutqazdi (
28.55va52.35ga qarshi24.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.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!