Mundarija (24)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Auditoriya: kim nima qaror qiladi?
- 2.2. Piramida tuzilishi
- 2.3. Noaniqlikni tushunarli ifodalash
- 2.4. Avtomatik hisobot
- 2.5. Grafik tamoyillari
- 2.6. Nima qilmaslik kerak
- 2.7. Pilot / A-B rejasi
- 2.8. Topshirish va keyingi qadamlar
- 2.9. Ijroiya xulosasi (executive summary) namunasi
- 2.10. Tuzoqlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Piramida: avval xulosa va tavsiya, keyin dalil; noaniqlik tushunarli tilda
- Misol 2 — Avtomatik hisobot generatori: raqamlar natijadan, qo'lda emas
- Misol 3 — Grafik tamoyillari: yomon va yaxshi grafik, elementlarini avtomatik tekshirish
- Misol 4 — Nima qilmaslik kerak: cherry-picking va p-hacking; pilot hajmi va quvvati
- 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
29.4-dars: To'liq loyiha: natijani taqdim etish
29-QISM — LOYIHALAR VA KARYERA · 4-dars
1. Kirish va motivatsiya
Oldingi darsda loyihaning texnik qismi tugadi. Baholash rejasi muhrlandi, 7 oylik vaqt bo'yicha CV da LogReg tanlandi, ehtimollar kalibrlandi va sig'imga mos siyosat qurildi. Test bir marta ochildi: model joriy qoidadan oyiga +11.25 mln so'm (SE 3.98) ko'p tejadi. Lekin bu natija hali hech qanday qarorni o'zgartirmadi. Qaror jadvalda, noutbukda yoki model kartasida qabul qilinmaydi. Uni rahbariyat va qo'ng'iroq markazi qabul qiladi, va ular AUC, SE yoki "top-K" tilida gapirmaydi.
Real vaziyat. DS jamoasi kuchli tahlilni 40 slaydli taqdimot bilan boshqaruv yig'ilishiga olib keldi. Birinchi 25 slayd ma'lumot, belgilar va modellar haqida edi, tavsiya esa 38-slaydda. Yig'ilishning 15-daqiqasida direktor so'radi: "Xo'sh, nima qilishimiz kerak va bu qancha turadi?" Javob uchun slaydlarni aylantirishga to'g'ri keldi. Keyin direktor bitta slayddagi "Toshkentda model 2 barobar yaxshi ishlaydi" degan raqamga yopishib oldi va "demak faqat Toshkentda joriy qilamiz" dedi. Aslida bu raqam 12 ta hududdan eng yaxshisi edi va tasodif ekanini hech kim tekshirmagan edi. Yig'ilish "yana bir bor ko'rib chiqamiz" bilan tugadi.
Bu darsda loyihaning uchinchi qismini qilamiz: natijani qaror qabul qiluvchiga yetkazish. Hisobot "piramida" tuzilishida bo'ladi: avval xulosa va tavsiya, keyin dalil. Noaniqlik tushunarli tilda ifodalanadi. Hisobot raqamlari koddan olinadi va qo'lda ko'chirilmaydi. Grafiklar avtomatik tekshiriladi. "Eng yaxshi segment" tuzog'ining narxini simulyatsiya bilan o'lchaymiz. Oxirida pilot rejasi va ikki sahifalik ijroiya xulosasini yozamiz.
Bu darsda to'g'ri natijani tushunarli, halol va qarorga tayyor qilib yetkazishni o'rganamiz.
Bu darsda:
- Auditoriya va "piramida" tuzilishi: xulosa → tavsiya → dalil
- Noaniqlik: ma'noli aniqlik, oraliqlar, "100 mijozdan N tasi", sezgirlik
- Avtomatik hisobot: natija lug'ati → shablon; kelib chiqish tekshiruvi
- Grafik tamoyillari va ularni avtomatik tekshirish
- Nima qilmaslik kerak: cherry-picking va p-hacking — simulyatsiya bilan
- Pilot / A-B rejasi: dizayn, namuna hajmi, quvvat, narx
- Topshirish va keyingi qadamlar; ijroiya xulosasi namunasi
ℹ Misollar real numpy/pandas/sklearn/matplotlib (Agg) bilan (Python 3.14). Natijalar 29.3 dagi yakuniy model va test oylari (20-22) bo'yicha qayta hisoblanadi. Generator va belgilar 29.1-29.3 dagi bilan aynan bir xil. Biznes parametrlari (samara 0.30, narx 20 ming so'm, zarar 0.45 * balans) — faraziy.
2. Nazariya — chuqur tushuntirish
2.1. Auditoriya: kim nima qaror qiladi?
Bitta natija turli auditoriyaga turlicha yetkaziladi:
| Auditoriya | Nima qaror qiladi | Unga kerak | Unga kerak emas |
|---|---|---|---|
| Boshqaruv (direktor) | davom etish, byudjet, pilot | pul, xatar, tavsiya, muddat | AUC, belgilar ro'yxati |
| Qo'ng'iroq markazi rahbari | ish tartibi, operatorlar | ro'yxat hajmi, "100 dan N tasi", sig'im qiymati | model turi |
| Kredit risk bo'limi | siyosatga mosligi, xatarlar | segmentlar, cheklovlar, noto'g'ri foydalanish | kod |
| IT / MLOps | joriy etish | quvur, xizmat, monitoring (27-qism) | biznes asoslash |
| DS hamkasblar (review) | metodologiya | reja, CV, test, sizish tekshiruvlari | — |
Bitta hujjatni hammaga yozish mumkin emas. Amalda qatlamli hujjat yoziladi: 1-sahifa (ijroiya xulosasi) boshqaruv uchun, keyingi bo'limlar biznes egalari uchun, ilovalar texnik tekshiruvchilar uchun.
2.2. Piramida tuzilishi
+--------------------------+
| XULOSA (1 gap) | "Model ro'yxati qoidadan oyiga
| + TAVSIYA (1 gap) | ~11 mln so'm ko'p tejaydi.
+--------------------------+ Pilot bilan joriy etamiz."
/ | \
+-----------+ +-----------+ +-----------+
| DALIL 1 | | DALIL 2 | | DALIL 3 | "3 oyning 3 tasida yaxshi"
| (natija) | | (ishonch) | | (xatar) | "farq 2*SE dan katta"
+-----------+ +-----------+ +-----------+ "samara - faraz, pilotda"
/ \ / \ / \
tafsilot, jadvallar, grafiklar, metodologiya (ilovalar)
O'QUVCHI QAYERDA TO'XTASA HAM, ENG MUHIMINI OLGAN BO'LADI.Vaziyat → murakkablik → savol → javob (SCQA) — kirish uchun qulay formula:
- Vaziyat: qo'ng'iroq markazi oyiga 200 mijozga qo'ng'iroq qiladi, ro'yxat qo'lda, bitta qoida bilan tuziladi.
- Murakkablik: qoida xavfli mijozlarning kichik qismini topadi, zarar hajmini hisobga olmaydi.
- Savol: ro'yxatni model bilan tuzsak, qancha tejaymiz va bu qanchalik ishonchli?
- Javob: oyiga taxminan +11 mln so'm, oraliq 3-19. Pilot tavsiya qilinadi.
Ilmiy maqola tartibi (kirish → usul → natija → xulosa) boshqaruv uchun teskari. U tadqiqotchi uchun tabiiy, lekin qaror qabul qiluvchi xulosani oxirida topadi.
2.3. Noaniqlikni tushunarli ifodalash
1. MA'NOLI ANIQLIK 42.3261 emas -> "taxminan 40, oraliq 30-50"
qoida: SE ning birinchi raqamigacha yaxlitlash
2. ORALIQ, NUQTA EMAS "+11 mln (oraliq 3-19)" - pastki chegara ham ijobiy
3. TABIIY CHASTOTALAR "ro'yxatdagi 100 mijozdan ~14 tasi xavfli
(oddiy mijozlarda ~2 tasi)" - foizdan tushunarliroq
4. SEZGIRLIK eng katta faraz (samara) bo'yicha: 0.15 / 0.30 / 0.45
-> "natija faraz bilan chiziqli o'zgaradi: 19 dan 66 gacha"
5. BILMAGANINI AYTISH "qo'ng'iroq samarasi o'lchanmagan - pilot shuni o'lchaydi"
6. ANIQLIK DARAJASI SO'ZLARI (izchil):
"deyarli aniq" - 2*SE dan ancha katta; "ehtimol" - 1-2 SE; "aniq emas" - < 1 SENoaniqlikni yashirish ishonchni oshirmaydi. Aksincha, birinchi xato topilganda butun hisobotga bo'lgan ishonch yo'qoladi. Noaniqlikni raqam bilan ko'rsatish esa tavsiyani mustahkamlaydi: "hatto pessimistik holatda ham +3" degan gap "+11.25" dan kuchliroq.
2.4. Avtomatik hisobot
TAHLIL KODI -> natija lug'ati R = {"tejam_m": 42.33, "farq": 11.25, ...}
|
v
SHABLON (markdown): "Model ... oyiga {farq:.1f} mln so'm ko'p tejaydi ..."
|
v
hisobot.md (+ grafiklar) -> kelib chiqish tekshiruvi -> xesh -> versiyaQoidalar:
- Hisobotdagi har son R dan olinadi. Qo'lda ko'chirilgan raqam birinchi yangilanishda eskirib qoladi.
- Kelib chiqish tekshiruvi: matndagi har son R ning biror qiymatining formatiga mos kelishi kerak. Mos kelmasa, bu "qo'lda tahrir" (2-misol).
- Matndagi xulosalar ham shart bilan: tavsiya matni
if farq > 2 * seshartidan tanlanadi. Natija o'zgarsa, tavsiya ham o'zgaradi. - Determinizm: bir xil ma'lumot bir xil hisobot beradi (xesh). Shunda hisobotni versiyalash 27.4-bob va "nima o'zgardi?" savoliga javob berish oson bo'ladi.
- Haqiqiy loyihada: Jinja2 shablon, Quarto yoki Jupyter + papermill. G'oya bir xil.
2.5. Grafik tamoyillari
5.12-5.13-darslardagi tamoyillar taqdimotda qat'iy qo'llanadi. Ularning ko'pchiligi avtomatik tekshiriladi:
| Tamoyil | Tekshiruv (matplotlib obyektidan) |
|---|---|
| Bitta grafik — bitta xabar; sarlavha xulosa | sarlavhada 5+ so'z (yoki raqam) |
| Ustun o'qi noldan | ax.get_ylim()[0] == 0; "yolg'on koeffitsienti" (Tufte) ≈ 1 |
| Spagetti yo'q | chiziqlar soni ≤ 4 |
| Ikkinchi y o'qi yo'q | egizak o'qlar (twinx) |
| Legenda o'rniga to'g'ridan-to'g'ri yozuv | ax.texts, legenda elementlari |
| Rang — ma'no uchun | noyob ranglar soni |
Yolg'on koeffitsienti = (grafikdagi vizual nisbat) / (ma'lumotdagi nisbat). 3-misolda kesilgan o'q 1.36 barobarlik farqni 11.5 barobar qilib ko'rsatdi, koeffitsient 8.4. Grafik fayl hajmi emas, uning elementlari tekshiriladi: fayl hajmi mazmun haqida hech narsa aytmaydi.
2.6. Nima qilmaslik kerak
CHERRY-PICKING ko'p segment/davr/metrikadan eng yaxshisini ko'rsatish
12 tasodifiy segmentdan eng yaxshisi o'rtachadan ~3.5 barobar yuqori
ko'rinadi (4-misol) - va yangi ma'lumotda o'rtachaga qaytadi
P-HACKING p < 0.05 chiqquncha kesim/test/belgi almashtirish
20 ta kesimda kamida bitta "topilma" ehtimoli ~0.64 (A/A da!)
HARKing natijani ko'rib, gipotezani "oldindan shunday edi" deb yozish
TEST -> VAL test yomon chiqsa, qayta sozlab, testni "yana bir marta" ochish 29.3-bob
YASHIRISH yomon oylar, yomon segmentlar, ishlamagan yondashuvlar
SOXTA ANIQLIK 42.3261 mln so'm; "AUC 0.8296 dan 0.8300 ga yaxshilandi"
KORRELYATSIYA "qo'ng'iroq qilinganlar kamroq kechikdi" (ular boshqa mijozlar!)
-> SABAB samara faqat tasodifiy nazorat guruhi bilan o'lchanadi 2.7-bob
HIMOYA: oldindan reja 29.3-bob, barcha segmentlar jadvali (tanlanganlari emas),
ko'p taqqoslash tuzatishi 11.9-bob, yangi ma'lumotda tasdiqlash2.7. Pilot / A-B rejasi
Loyihaning eng katta noaniqligi model emas, qo'ng'iroq samarasi (0.30 — faraz). 1-misolda tejam unga chiziqli bog'liq: samara 0.15 bo'lsa oyiga ~19 mln, 0.45 bo'lsa ~66 mln. Bu parametrni faqat tasodifiy tajriba o'lchay oladi (27.13, 28.11).
DIZAYN: har oy model bo'yicha top-400 -> tasodifan 200 tasiga qo'ng'iroq (davolash),
200 tasi - nazorat (qo'ng'iroqsiz)
nega top-400: sig'im (200) to'liq ishlatiladi, ikki guruh bir xil populyatsiyadan
ASOSIY METRIKA: DPD60 ulushi (davolash va nazorat), ikki ulush testi 11.4-bob
HAJM (alfa 0.05, quvvat 0.8):
n = [ z_a * sqrt(2 p q) + z_b * sqrt(p0 q0 + p1 q1) ]^2 / (p0 - p1)^2
p0 = 0.113 (top-400 da), samara 0.30 -> p1 = 0.079 -> n ~ 1191 har guruhda -> ~6 oy
samara 0.15 bo'lsa -> ~5139 har guruhda -> ~26 oy (amalda o'lchab bo'lmaydi)
NARX: top-200 o'rniga tasodifiy yarim top-400 -> ~11.5 mln so'm/oy yo'qotish
TO'SIQLAR: shikoyatlar, qo'ng'iroqdan keyingi to'lov kechikishi, operator yuklamasi
TO'XTATISH QOIDASI: oldindan (masalan, shikoyatlar 2 barobar oshsa)
TAHLIL REJASI: oldindan yoziladi va muhrlanadi (29.3 dagi kabi)Muhim halol topilma. 29.1 dagi namuna rejada pilot "2-3 oy" deb yozilgan edi. Hajm hisobi buni rad etdi: 3 oyda (600 + 600) quvvat atigi 0.517 (4-misol). Ya'ni haqiqiy samara 0.30 bo'lsa ham, tajriba uni ikki holatdan birida "ko'rmaydi". Muddat hisobdan kelib chiqadi, xohishdan emas. Agar 6 oy juda uzoq bo'lsa, variantlar bor: pilot davrida sig'imni oshirish, oraliq metrika (masalan, 30 kunlik to'lov) yoki kichikroq samarani aniqlashdan voz kechish. Har birining narxi hisoblanadi.
2.8. Topshirish va keyingi qadamlar
TOPSHIRISH PAKETI (loyiha "egasiz" qolmasligi uchun):
[ ] kod repozitoriysi: sql/, src/, tests/ (snapshot testi!), README 29.6-bob
[ ] ma'lumot kartasi 29.2-bob va model kartasi 29.3-bob, reja muhrlari
[ ] model paketi va registr yozuvi (27.5-27.6)
[ ] monitoring: kalibrlash (p/haqiqiy), PSI, oylik tejam; chegaralar (27.11-27.12)
[ ] qayta o'qitish qoidasi va javobgar shaxs
[ ] pilot rejasi va tahlil rejasi (muhrlangan)
[ ] ma'lum cheklovlar va "foydalanmang" ro'yxati
[ ] hisobot generatori: har oy bir buyruq bilan yangilanadi
KEYINGI QADAMLAR (ustuvorlik bo'yicha):
1. pilot (samara - eng katta noaniqlik)
2. kalibrlash monitoringi (testda 0.880)
3. recall farqlari segmentlar bo'yicha 29.11-bob
4. harakat turlari: SMS + qo'ng'iroq (arzon harakat bilan sig'imni kengaytirish)
5. uplift modeli 28.11-bob - pilot ma'lumoti yig'ilgach: "kimga qo'ng'iroq TA'SIR qiladi?"5-band muhim. Hozirgi model "kim kechikadi?" degan savolga javob beradi. Biznes esa aslida "kimga qo'ng'iroq yordam beradi?" deb so'raydi. Bu uplift savoli 28.11-bob. Unga faqat pilotdagi tasodifiy nazorat guruhi ma'lumoti bilan javob berish mumkin. Pilot shu sababli ikki marta qimmatli.
2.9. Ijroiya xulosasi (executive summary) namunasi
Quyidagi namuna 1-4-misollardagi raqamlar bilan to'ldirilgan. Haqiqiy loyihada u 2-misoldagi kabi shablondan avtomatik yaratiladi. Hajmi: ikki sahifa, birinchi yarim sahifada qaror uchun kerakli hamma narsa bor.
# Kredit karta kechikishi: profilaktik qo'ng'iroqlar ro'yxati
**Ijroiya xulosasi** · DS jamoasi va kredit risk bo'limi · 2025-12 · v1 (namuna)
## Xulosa va tavsiya
Model bilan tuzilgan qo'ng'iroq ro'yxati joriy qoidadan (limitdan foydalanish ulushi)
oyiga taxminan **+11 mln so'm** ko'p zarar tejaydi (ehtimoliy oraliq 3-19 mln).
**Tavsiya:** modelni nazorat guruhli pilot bilan joriy etish. Pilot ~6 oy davom etadi
va loyihaning eng katta noaniqligini - qo'ng'iroq samarasini - o'lchaydi.
## Nima uchun ishonsa bo'ladi
- Sinov oylarida (2025-08 - 2025-10, model ularni ko'rmagan) model 3 oyning 3 tasida
qoidadan yaxshi bo'ldi. Oldingi 7 oyda ham 7 tadan 7 tasida.
- Model ro'yxatidagi har 100 mijozdan ~14 tasi 60+ kun kechikadigan mijoz.
Qoida ro'yxatida ~12 tasi, oddiy mijozlarda ~2 tasi.
Model bundan tashqari zarari katta mijozlarni oldinga qo'yadi.
- Baholash rejasi natijadan oldin yozilgan, sinov ma'lumoti bir marta ochilgan.
## Qancha tejaydi (oyiga, mln so'm)
| | Model | Joriy qoida |
|---|---|---|
| Kutilgan tejam (sinov oylari) | ~40 (30-50) | ~31 |
| Qo'ng'iroqlar | 200 | 200 |
Yuqori yuklamali oylarda tejam ko'proq, oddiy oylarda ~31 atrofida (oldingi 7 oy).
## Asosiy noaniqlik: qo'ng'iroq samarasi
Hisob-kitob "qo'ng'iroq jiddiy kechikishlarning 30% ini oldini oladi" degan FARAZGA
tayanadi. Samara 15% bo'lsa tejam ~19 mln, 45% bo'lsa ~66 mln bo'ladi.
Buni faqat tajriba o'lchay oladi.
## Pilot rejasi
- Har oy model bo'yicha 400 eng xavfli mijozdan tasodifiy 200 tasiga qo'ng'iroq,
200 tasi nazorat.
- Muddat ~6 oy (3 oyda tajriba haqiqiy samarani faqat ~50% ehtimol bilan ko'radi).
- Narxi: pilot davrida ~11.5 mln so'm/oy kam tejam (nazorat guruhiga qo'ng'iroq yo'q).
- To'xtatish qoidasi: shikoyatlar ikki barobar oshsa.
## Xatarlar va cheklovlar
- Model sinov oylarida xavfni ~12% kam baholadi (iqtisodiy yuklama oshgan):
oylik kalibrlash nazorati va qayta kalibrlash kiritiladi.
- Segmentlar bo'yicha jiddiy og'ish topilmadi; guruhlar orasidagi "ushlash" farqlari
adolatlilik tahlilida ko'rib chiqiladi.
- Model kredit berish yoki limit qarorlari uchun EMAS.
## Qaror uchun so'raladi
1. Pilotni tasdiqlash (6 oy, ~11.5 mln so'm/oy vaqtinchalik narx).
2. Kredit risk bo'limidan pilot egasini tayinlash.
3. Keyingi ko'rib chiqish: pilotning 3-oyida (oraliq, faqat xavfsizlik ko'rsatkichlari).
---
*Ilova (2-sahifa): metodologiya, CV va test jadvallari, segmentlar, sig'im egri chizig'i,
ma'lumot va model kartalari. Hamma raqamlar hisobot generatoridan (versiya xeshi bilan).*2.10. Tuzoqlar
Asosiy tuzoqlar: tavsiyani oxiriga qo'yish (ilmiy maqola tartibi); hammaga bitta hujjat; soxta aniqlik (4 xonali kasrlar); oraliqsiz nuqta baho; foizlar o'rniga tabiiy chastotalar ishlatilmasligi; faraziy parametrni fakt sifatida taqdim etish; raqamlarni qo'lda ko'chirish; kesilgan o'qli ustun grafik, ikki y o'qi, spagetti chiziqlar; "Grafik 1" kabi sarlavhalar; ko'p segmentdan eng yaxshisini ko'rsatish; p < 0.05 chiqquncha kesim almashtirish; korrelyatsiyadan samara xulosa qilish; pilot muddatini hisobsiz belgilash; pilotning narxini aytmaslik; topshirish paketisiz loyihani "tugatish".
3. Tez ma'lumotnoma
import math
import re
# ma'noli aniqlik
def yaxlitla(x, se):
qadam = 10 ** math.floor(math.log10(2 * se))
return round(x / qadam) * qadam
# tabiiy chastota
print(f"ro'yxatdagi 100 mijozdan ~{100 * precision:.0f} tasi")
# shablon + kelib chiqish tekshiruvi
hisobot = SHABLON.format(**R)
begona = [s for s in re.findall(r"\d+(?:\.\d+)?", hisobot) if s not in ruxsat_formatlar(R)]
# grafik tekshiruvi (Agg)
past = ax.get_ylim()[0] # ustun grafik: 0 bo'lishi shart
yolgon = ((h_max - past) / (h_min - past)) / (h_max / h_min)
# pilot hajmi (ikki ulush, alfa 0.05, quvvat 0.8)
n = ((1.96 * math.sqrt(2 * pb * (1 - pb))
+ 0.8416 * math.sqrt(p0 * (1 - p0) + p1 * (1 - p1))) / (p0 - p1)) ** 2Taqdimot nazorat ro'yxati
| Savol | Ha/Yo'q |
|---|---|
| Birinchi 2 gapda xulosa va tavsiya bormi? | |
| Har raqamda ma'noli aniqlik va oraliq bormi? | |
| Faraziy parametrlar aniq belgilanganmi va sezgirlik ko'rsatilganmi? | |
| Hamma raqam koddan olinganmi (kelib chiqish tekshiruvi)? | |
| Grafiklar tekshiruvdan o'tganmi (0 dan, ≤4 qator, xulosa-sarlavha)? | |
| Segmentlar to'liq ko'rsatilganmi (tanlanganlari emas)? | |
| Pilot hajmi hisoblanganmi va narxi aytilganmi? | |
| "Qaror uchun so'raladi" bo'limi bormi? |
Taqdimot xulosasi
auditoriya -> piramida: xulosa + tavsiya -> 3 dalil -> ilovalar
noaniqlik: ma'noli aniqlik, oraliq, "100 dan N tasi", sezgirlik, "bilmaymiz"
hisobot: natija lug'ati -> shablon; kelib chiqish tekshiruvi; xesh
grafik: bitta xabar, 0 dan, ≤4 qator, twinx yo'q; yolg'on koeffitsienti ≈ 1
qilmaslik: cherry-picking, p-hacking, soxta aniqlik
pilot: tasodifiy nazorat, hajm hisobi, quvvat simulyatsiyasi, narx; topshirish paketi4. Batafsil misollar
Misollar real numpy/pandas/sklearn/matplotlib bilan (Python 3.14). Har misol mustaqil ishlaydi, generator va belgilar jadvali 29.1-29.3 dagi bilan aynan bir xil. Yakuniy model — LogReg (as-of 7-18 da o'qitilgan), siyosat — top-200
p * zarar, natijalar test oylari (20-22) bo'yicha.
Misol 1 — Piramida: avval xulosa va tavsiya, keyin dalil; noaniqlik tushunarli tilda
"""Piramida: avval xulosa va tavsiya, keyin dalil; noaniqlik tushunarli tilda."""
import math
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
HUDUDLAR = ["Toshkent", "Samarqand", "Farg'ona", "Andijon", "Buxoro", "Qashqadaryo"]
OYLAR = 24 # 1 = 2024-01, ..., 24 = 2025-12
BELGILAR = ["yosh", "daromad", "limit", "avto_tolov", "maosh_loyihasi", "staj",
"util", "util_3", "util_ozg", "kechikish_6", "tolov_nisbati_3",
"tolov_kuni_3", "naqd_soni_3", "naqd_summa_3", "xarid_soni_3"]
K, SAMARA, NARX = 200, 0.30, 0.02 # oyiga qo'ng'iroq, oldini olish, mln so'm
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def oy_nomi(m):
return f"{2024 + (m - 1) // 12}-{(m - 1) % 12 + 1:02d}"
def yarat_bank(seed=29, n=6000):
"""Kredit karta portfeli: mijozlar, hisobvaraqlar, tolovlar, tranzaksiyalar."""
rng = np.random.default_rng(seed)
ids = np.arange(1, n + 1)
yosh = rng.integers(21, 66, n)
maosh = rng.random(n) < 0.45
daromad = np.round(np.exp(rng.normal(np.log(6.0), 0.45, n)), 2) # mln so'm
ochilgan = np.where(rng.random(n) < 0.7, rng.integers(-40, 1, n),
rng.integers(1, 20, n))
xavf = (rng.normal(0, 1, n) - 0.5 * maosh - 0.8 * np.log(daromad / 6)
+ 0.015 * (35 - yosh)) # yashirin: moliyaviy xavf
unut = rng.normal(0, 1, n) + 0.03 * (35 - yosh) # yashirin: unutuvchanlik
avto = (rng.random(n) < sigmoid(-0.2 - 0.9 * unut)).astype(int)
limit = np.round(daromad * rng.uniform(1.5, 3.5, n), 1)
hudud = rng.choice(HUDUDLAR, n, p=[0.34, 0.15, 0.14, 0.13, 0.12, 0.12])
mj = pd.DataFrame({"mijoz_id": ids, "yosh": yosh, "hudud": hudud,
"daromad": daromad, "limit": limit, "avto_tolov": avto,
"maosh_loyihasi": maosh.astype(int), "ochilgan_oy": ochilgan})
s, oldingi, ketma = rng.normal(0, 1, n), np.zeros(n, int), np.zeros(n, int)
hv, tl, tr = [], [], []
for m in range(1, OYLAR + 1):
faol = (ochilgan <= m) & (ketma < 3) # 3 ketma-ket kechikish - defolt
s = 0.75 * s + 0.66 * rng.normal(0, 1, n)
d = 0.8 * xavf + 0.7 * s + 0.15 * (m % 12 == 1) + 0.08 * max(m - 18, 0)
util = np.clip(sigmoid(-0.4 + 0.55 * d + rng.normal(0, 0.5, n)), 0.01, 1)
balans = np.round(util * limit, 3)
min_t = np.round(np.maximum(0.05 * balans, 0.05), 3)
for turi, lam in [("xarid", np.exp(1.2 + 0.15 * np.log(daromad / 6) - 0.1 * d)),
("naqd", np.exp(-1.6 + 0.6 * d)),
("onlayn", np.exp(0.6 - 0.02 * (yosh - 35)))]:
i = np.repeat(np.arange(n), rng.poisson(lam) * faol)
summa = (rng.uniform(0.2, 1.5, i.size) * (1 + 0.3 * d[i].clip(0))
if turi == "naqd" else np.exp(rng.normal(np.log(0.25), 0.8, i.size)))
tr.append(pd.DataFrame({"mijoz_id": ids[i], "oy": m,
"kun": rng.integers(1, 29, i.size), "turi": turi,
"summa": np.round(summa, 3)}))
q = rng.random(n) < sigmoid(-3.7 + 1.3 * d + 1.2 * (util > 0.85)
+ 3.8 * (oldingi == 2)) # qiyinchilik
u = ~q & (rng.random(n) < sigmoid(-3.4 + 1.1 * unut - 1.8 * avto)
* (oldingi != 1)) # unutish
miss = (q | u) & faol
ulush = sigmoid(0.3 - 1.2 * d + rng.normal(0, 0.5, n))
summa = np.where(miss, min_t * rng.uniform(0, 0.6, n),
min_t + (balans - min_t).clip(0) * ulush)
tolaydi = faol & (~miss | (rng.random(n) < 0.4))
kun = np.clip(np.round(6 + 14 * sigmoid(unut + 0.5 * d)
+ rng.normal(0, 3, n)), 1, 25)
hv.append(pd.DataFrame({"mijoz_id": ids[faol], "oy": m,
"balans": balans[faol], "min_tolov": min_t[faol]}))
if m < OYLAR: # m-oy hisobvarag'i (m+1)-oyda to'lanadi
tl.append(pd.DataFrame({"mijoz_id": ids[tolaydi], "hisob_oy": m,
"tolov_oy": m + 1,
"tolov_kuni": kun[tolaydi].astype(int),
"summa": np.round(summa[tolaydi], 3)}))
ketma = np.where(faol, np.where(miss, ketma + 1, 0), ketma)
oldingi = np.where(miss, np.where(q, 2, 1), 0)
return {"mijozlar": mj, "hisobvaraqlar": pd.concat(hv, ignore_index=True),
"tolovlar": pd.concat(tl, ignore_index=True),
"tranzaksiyalar": pd.concat(tr, ignore_index=True)}
def orta(a):
"""Qator bo'yicha NaN siz o'rtacha (bo'sh qatorda NaN, ogohlantirishsiz)."""
k = (~np.isnan(a)).sum(axis=1)
return np.where(k > 0, np.nansum(a, axis=1) / np.maximum(k, 1), np.nan)
def belgilar_jadvali(bank, oylar=range(7, 23)):
"""Har as-of oy t (oy oxiri): faqat t gacha ma'lum belgilar + t, t+1 natijasi."""
mj = bank["mijozlar"]
ids, kol = mj["mijoz_id"].to_numpy(), np.arange(1, OYLAR + 1)
def mat(df, qiymat, ustun="oy"):
return (df.pivot(index="mijoz_id", columns=ustun, values=qiymat)
.reindex(index=ids, columns=kol).to_numpy(dtype=float))
hv, tl = bank["hisobvaraqlar"], bank["tolovlar"]
bal, mint = mat(hv, "balans"), mat(hv, "min_tolov")
tol, kun = mat(tl, "summa", "hisob_oy"), mat(tl, "tolov_kuni", "hisob_oy")
bor = ~np.isnan(bal)
miss = bor & ~(np.nan_to_num(tol) >= mint - 1e-9) # minimal to'lov qilinmagan
g = (bank["tranzaksiyalar"].groupby(["turi", "mijoz_id", "oy"])["summa"]
.agg(["size", "sum"]))
def tr_mat(turi, ust):
return (g.loc[turi, ust].unstack("oy").reindex(index=ids, columns=kol)
.fillna(0).to_numpy())
naqd_n, naqd_s = tr_mat("naqd", "size"), tr_mat("naqd", "sum")
xarid_n = tr_mat("xarid", "size")
util = bal / mj["limit"].to_numpy()[:, None]
och = mj["ochilgan_oy"].to_numpy()
qismlar = []
for t in oylar:
c = t - 1 # t-oy ustuni (0 dan)
ok = (och <= t - 1) & bor[:, c] & bor[:, c - 1] & ~miss[:, c - 1]
u3 = util[:, c - 3]
f = mj.drop(columns="ochilgan_oy").assign(
oy=t, staj=t - och, util=util[:, c], util_3=orta(util[:, c - 2:c + 1]),
util_ozg=util[:, c] - np.where(np.isnan(u3), util[:, c], u3),
kechikish_6=miss[:, max(c - 6, 0):c].sum(axis=1),
tolov_nisbati_3=orta(np.nan_to_num(tol[:, c - 3:c]) / bal[:, c - 3:c]),
tolov_kuni_3=orta(kun[:, c - 3:c]),
naqd_soni_3=naqd_n[:, c - 2:c + 1].sum(axis=1),
naqd_summa_3=naqd_s[:, c - 2:c + 1].sum(axis=1),
xarid_soni_3=xarid_n[:, c - 2:c + 1].sum(axis=1),
balans=bal[:, c], y30=(miss[:, c] | miss[:, c + 1]).astype(int),
y60=(miss[:, c] & miss[:, c + 1]).astype(int))
qismlar.append(f[ok])
df = pd.concat(qismlar, ignore_index=True)
df["tolov_kuni_3"] = df["tolov_kuni_3"].fillna(25.0)
df["tolov_nisbati_3"] = df["tolov_nisbati_3"].fillna(0.0)
df["zarar"] = 0.45 * df["balans"] # DPD60 bo'lsa kutilgan zarar
df["qiymat"] = SAMARA * df["zarar"] * df["y60"] - NARX # qo'ng'iroq qiymati
return df
def top_k(d, ball, k=K):
oy = d["oy"].to_numpy()
return np.concatenate([g[np.argsort(-ball[g], kind="stable")[:k]]
for g in [np.flatnonzero(oy == m) for m in np.unique(oy)]])
def yaxlitla(x, se):
"""Ma'noli aniqlik: SE ning birinchi raqamigacha yaxlitlash (soxta aniqlik yo'q)."""
qadam = 10 ** math.floor(math.log10(2 * se))
return round(x / qadam) * qadam
def main() -> None:
df = belgilar_jadvali(yarat_bank())
oquv, test = df[df.oy <= 18], df[df.oy.between(20, 22)].reset_index(drop=True)
m = make_pipeline(StandardScaler(), LogisticRegression(max_iter=2000))
p = m.fit(oquv[BELGILAR], oquv.y60).predict_proba(test[BELGILAR])[:, 1]
z, y, q = test.zarar.to_numpy(), test.y60.to_numpy(), test.qiymat.to_numpy()
oylar = test.oy.nunique()
tm, tq = top_k(test, p * z), top_k(test, test.util.to_numpy())
rng = np.random.default_rng(3)
guruh = [np.flatnonzero(test.oy.to_numpy() == m_) for m_ in range(20, 23)]
bs_m, bs_d = [], []
for _ in range(300):
idx = np.concatenate([rng.choice(g, len(g)) for g in guruh])
sub = test.iloc[idx].reset_index(drop=True)
a = sub.qiymat.to_numpy()[top_k(sub, p[idx] * sub.zarar.to_numpy())].sum() / oylar
b = sub.qiymat.to_numpy()[top_k(sub, sub.util.to_numpy())].sum() / oylar
bs_m.append(a)
bs_d.append(a - b)
tejam_m, farq = q[tm].sum() / oylar, (q[tm].sum() - q[tq].sum()) / oylar
se_m, se_d = np.std(bs_m, ddof=1), np.std(bs_d, ddof=1)
print("=== 1. Xom natija (texnik) ===")
print(f" tejam: model {tejam_m:.4f} mln so'm/oy (SE {se_m:.4f}), "
f"qoidadan farq {farq:+.4f} (SE {se_d:.4f})")
prec, prec_q = y[tm].mean(), y[tq].mean()
print(f" precision@200: model {prec:.4f}, qoida {prec_q:.4f}; bazaviy {y.mean():.4f}")
print("\n=== 2. Ma'noli aniqlik: rahbarga qanday raqam aytiladi ===")
lo, hi = tejam_m - 2 * se_m, tejam_m + 2 * se_m
print(f" soxta aniqlik: \"{tejam_m:.2f} mln so'm\"")
print(f" ma'noli: \"taxminan {yaxlitla(tejam_m, se_m):.0f} mln so'm, "
f"ehtimoliy oraliq {yaxlitla(lo, se_m):.0f}-{yaxlitla(hi, se_m):.0f}\"")
print(f" qoidadan farq: \"taxminan +{yaxlitla(farq, se_d):.0f} mln so'm/oy "
f"(oraliq {yaxlitla(farq - 2 * se_d, se_d):.0f}-{yaxlitla(farq + 2 * se_d, se_d):.0f})\"")
print("\n=== 3. Tabiiy chastotalar: \"100 mijozdan N tasi\" ===")
print(f" oddiy mijozlar: har 100 tadan ~{100 * y.mean():.0f} tasi 60+ kun kechikadi")
print(f" model ro'yxati: har 100 tadan ~{100 * prec:.0f} tasi "
f"(qoida ro'yxatida ~{100 * prec_q:.0f} tasi)")
saqlanadi = K * prec * SAMARA
print(f" oyiga 200 qo'ng'iroq -> ~{K * prec:.0f} ta xavfli mijozga yetamiz, "
f"ulardan ~{saqlanadi:.0f} tasi kechikishdan saqlanadi (samara 0.30 bo'lsa)")
print("\n=== 4. Eng katta noaniqlik: faraziy samara (sezgirlik) ===")
for s in [0.15, 0.30, 0.45]:
t = (s * (z * y)[tm].sum() - NARX * len(tm)) / oylar
print(f" samara {s:.2f}: tejam {t:5.1f} mln so'm/oy")
print("\n=== 5. Piramida (natijadan yig'ildi) ===")
xulosa = (f"Model qo'ng'iroq ro'yxatini joriy qoidadan yaxshiroq tuzadi: oyiga taxminan "
f"+{yaxlitla(farq, se_d):.0f} mln so'm qo'shimcha tejam.")
oy_t = test.oy.to_numpy()
oyma_oy = [q[tm][oy_t[tm] == m_].sum() > q[tq][oy_t[tq] == m_].sum()
for m_ in range(20, 23)]
tavsiya = ("Tavsiya: modelni nazorat guruhli pilot bilan ishga tushirish; pilot "
"qo'ng'iroq samarasini o'lchaydi (hajmi 4-misolda).")
dalillar = [
f"model ro'yxatida xavfli mijozlar ~{100 * prec:.0f}%, qoidada ~{100 * prec_q:.0f}%",
f"sinov oylarida model {sum(oyma_oy)} oyning {len(oyma_oy)} tasida qoidadan yaxshi",
f"farq tasodif emas: +{farq:.1f} (SE {se_d:.1f}), 2*SE dan katta: {farq > 2 * se_d}",
]
print(" 1) " + xulosa)
print(" 2) " + tavsiya)
for d in dalillar:
print(" - " + d)
print(" 4) Noaniqlik: qo'ng'iroq samarasi faraziy 0.30-bob - pilotda o'lchanadi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Xom natija (texnik) ===
tejam: model 42.3261 mln so'm/oy (SE 5.2249), qoidadan farq +11.2507 (SE 3.9759)
precision@200: model 0.1350, qoida 0.1167; bazaviy 0.0233
=== 2. Ma'noli aniqlik: rahbarga qanday raqam aytiladi ===
soxta aniqlik: "42.33 mln so'm"
ma'noli: "taxminan 40 mln so'm, ehtimoliy oraliq 30-50"
qoidadan farq: "taxminan +11 mln so'm/oy (oraliq 3-19)"
=== 3. Tabiiy chastotalar: "100 mijozdan N tasi" ===
oddiy mijozlar: har 100 tadan ~2 tasi 60+ kun kechikadi
model ro'yxati: har 100 tadan ~14 tasi (qoida ro'yxatida ~12 tasi)
oyiga 200 qo'ng'iroq -> ~27 ta xavfli mijozga yetamiz, ulardan ~8 tasi kechikishdan saqlanadi (samara 0.30 bo'lsa)
=== 4. Eng katta noaniqlik: faraziy samara (sezgirlik) ===
samara 0.15: tejam 19.2 mln so'm/oy
samara 0.30: tejam 42.3 mln so'm/oy
samara 0.45: tejam 65.5 mln so'm/oy
=== 5. Piramida (natijadan yig'ildi) ===
1) Model qo'ng'iroq ro'yxatini joriy qoidadan yaxshiroq tuzadi: oyiga taxminan +11 mln so'm qo'shimcha tejam.
2) Tavsiya: modelni nazorat guruhli pilot bilan ishga tushirish; pilot qo'ng'iroq samarasini o'lchaydi (hajmi 4-misolda).
- model ro'yxatida xavfli mijozlar ~14%, qoidada ~12%
- sinov oylarida model 3 oyning 3 tasida qoidadan yaxshi
- farq tasodif emas: +11.3 (SE 4.0), 2*SE dan katta: True
4) Noaniqlik: qo'ng'iroq samarasi faraziy 0.30-bob - pilotda o'lchanadiNatija tahlili.
1-bo'lim — xom natija, tahlilchi ko'radigan shaklda: tejam 42.3261 (SE 5.2249), qoidadan farq +11.2507 (SE 3.9759). To'rt xonali kasrlar va SE — tekshiruv uchun kerak, rahbar uchun emas.
2-bo'lim — ma'noli aniqlik. SE ~5 bo'lsa, birlar xonasi ham noaniq. Shuning uchun 42.33 o'rniga "taxminan 40, oraliq 30-50" deyiladi. Farq uchun "+11, oraliq 3-19" deyiladi. Oraliqning pastki chegarasi ham musbat, va bu tavsiyaning eng kuchli dalili. Yaxlitlash qoidasi mexanik (SE ning birinchi raqamigacha), shuning uchun "chiroyli" raqam tanlash imkoni yo'q.
3-bo'lim — tabiiy chastotalar. "Precision 0.1350" o'rniga "ro'yxatdagi 100 mijozdan 14 tasi" deyiladi, va taqqoslash uchun qo'shiladi: oddiy mijozlarda ~2 tasi, qoida ro'yxatida ~12 tasi. Keyin qo'ng'iroq markazi tilida: oyiga 200 qo'ng'iroq bilan ~27 xavfli mijozga yetamiz, samara 0.30 bo'lsa ~8 tasi kechikishdan saqlanadi. "8 ta" rahbarni hayratlantirmasligi mumkin. Lekin u halol, va har biri o'rtacha bir necha mln so'mlik zarar ekanini qo'shsak, raqam ma'noga ega bo'ladi. Muhim nozik joy: model va qoida ro'yxatlaridagi xavfli mijozlar ulushi yaqin (~14 va ~12). Asosiy farq zarar hajmida: model katta balansli xavfli mijozlarni oldinga qo'yadi 29.1-bob.
4-bo'lim — sezgirlik. Tejam faraziy samaraga chiziqli bog'liq: 0.15 da 19.2, 0.30 da 42.3, 0.45 da 65.5. Bu diapazon SE dan ancha keng. Demak loyihaning eng katta noaniqligi statistik shovqin emas, o'lchanmagan biznes parametri. Bu jumla pilot tavsiyasini asoslaydi (4-misol).
5-bo'lim — piramida koddan yig'ildi. Xulosa bir gap, tavsiya bir gap, keyin uchta dalil (ulush, oylar, sezilarlilik) va noaniqlik. Hamma raqam natijadan olingan: "3 oyning 3 tasida" matni ham oylik taqqoslashdan hisoblangan.
Misol 2 — Avtomatik hisobot generatori: raqamlar natijadan, qo'lda emas
"""Avtomatik hisobot generatori: raqamlar natijadan olinadi, qo'lda ko'chirilmaydi."""
import hashlib
import pathlib
import re
import tempfile
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
HUDUDLAR = ["Toshkent", "Samarqand", "Farg'ona", "Andijon", "Buxoro", "Qashqadaryo"]
OYLAR = 24 # 1 = 2024-01, ..., 24 = 2025-12
BELGILAR = ["yosh", "daromad", "limit", "avto_tolov", "maosh_loyihasi", "staj",
"util", "util_3", "util_ozg", "kechikish_6", "tolov_nisbati_3",
"tolov_kuni_3", "naqd_soni_3", "naqd_summa_3", "xarid_soni_3"]
K, SAMARA, NARX = 200, 0.30, 0.02 # oyiga qo'ng'iroq, oldini olish, mln so'm
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def oy_nomi(m):
return f"{2024 + (m - 1) // 12}-{(m - 1) % 12 + 1:02d}"
def yarat_bank(seed=29, n=6000):
"""Kredit karta portfeli: mijozlar, hisobvaraqlar, tolovlar, tranzaksiyalar."""
rng = np.random.default_rng(seed)
ids = np.arange(1, n + 1)
yosh = rng.integers(21, 66, n)
maosh = rng.random(n) < 0.45
daromad = np.round(np.exp(rng.normal(np.log(6.0), 0.45, n)), 2) # mln so'm
ochilgan = np.where(rng.random(n) < 0.7, rng.integers(-40, 1, n),
rng.integers(1, 20, n))
xavf = (rng.normal(0, 1, n) - 0.5 * maosh - 0.8 * np.log(daromad / 6)
+ 0.015 * (35 - yosh)) # yashirin: moliyaviy xavf
unut = rng.normal(0, 1, n) + 0.03 * (35 - yosh) # yashirin: unutuvchanlik
avto = (rng.random(n) < sigmoid(-0.2 - 0.9 * unut)).astype(int)
limit = np.round(daromad * rng.uniform(1.5, 3.5, n), 1)
hudud = rng.choice(HUDUDLAR, n, p=[0.34, 0.15, 0.14, 0.13, 0.12, 0.12])
mj = pd.DataFrame({"mijoz_id": ids, "yosh": yosh, "hudud": hudud,
"daromad": daromad, "limit": limit, "avto_tolov": avto,
"maosh_loyihasi": maosh.astype(int), "ochilgan_oy": ochilgan})
s, oldingi, ketma = rng.normal(0, 1, n), np.zeros(n, int), np.zeros(n, int)
hv, tl, tr = [], [], []
for m in range(1, OYLAR + 1):
faol = (ochilgan <= m) & (ketma < 3) # 3 ketma-ket kechikish - defolt
s = 0.75 * s + 0.66 * rng.normal(0, 1, n)
d = 0.8 * xavf + 0.7 * s + 0.15 * (m % 12 == 1) + 0.08 * max(m - 18, 0)
util = np.clip(sigmoid(-0.4 + 0.55 * d + rng.normal(0, 0.5, n)), 0.01, 1)
balans = np.round(util * limit, 3)
min_t = np.round(np.maximum(0.05 * balans, 0.05), 3)
for turi, lam in [("xarid", np.exp(1.2 + 0.15 * np.log(daromad / 6) - 0.1 * d)),
("naqd", np.exp(-1.6 + 0.6 * d)),
("onlayn", np.exp(0.6 - 0.02 * (yosh - 35)))]:
i = np.repeat(np.arange(n), rng.poisson(lam) * faol)
summa = (rng.uniform(0.2, 1.5, i.size) * (1 + 0.3 * d[i].clip(0))
if turi == "naqd" else np.exp(rng.normal(np.log(0.25), 0.8, i.size)))
tr.append(pd.DataFrame({"mijoz_id": ids[i], "oy": m,
"kun": rng.integers(1, 29, i.size), "turi": turi,
"summa": np.round(summa, 3)}))
q = rng.random(n) < sigmoid(-3.7 + 1.3 * d + 1.2 * (util > 0.85)
+ 3.8 * (oldingi == 2)) # qiyinchilik
u = ~q & (rng.random(n) < sigmoid(-3.4 + 1.1 * unut - 1.8 * avto)
* (oldingi != 1)) # unutish
miss = (q | u) & faol
ulush = sigmoid(0.3 - 1.2 * d + rng.normal(0, 0.5, n))
summa = np.where(miss, min_t * rng.uniform(0, 0.6, n),
min_t + (balans - min_t).clip(0) * ulush)
tolaydi = faol & (~miss | (rng.random(n) < 0.4))
kun = np.clip(np.round(6 + 14 * sigmoid(unut + 0.5 * d)
+ rng.normal(0, 3, n)), 1, 25)
hv.append(pd.DataFrame({"mijoz_id": ids[faol], "oy": m,
"balans": balans[faol], "min_tolov": min_t[faol]}))
if m < OYLAR: # m-oy hisobvarag'i (m+1)-oyda to'lanadi
tl.append(pd.DataFrame({"mijoz_id": ids[tolaydi], "hisob_oy": m,
"tolov_oy": m + 1,
"tolov_kuni": kun[tolaydi].astype(int),
"summa": np.round(summa[tolaydi], 3)}))
ketma = np.where(faol, np.where(miss, ketma + 1, 0), ketma)
oldingi = np.where(miss, np.where(q, 2, 1), 0)
return {"mijozlar": mj, "hisobvaraqlar": pd.concat(hv, ignore_index=True),
"tolovlar": pd.concat(tl, ignore_index=True),
"tranzaksiyalar": pd.concat(tr, ignore_index=True)}
def orta(a):
"""Qator bo'yicha NaN siz o'rtacha (bo'sh qatorda NaN, ogohlantirishsiz)."""
k = (~np.isnan(a)).sum(axis=1)
return np.where(k > 0, np.nansum(a, axis=1) / np.maximum(k, 1), np.nan)
def belgilar_jadvali(bank, oylar=range(7, 23)):
"""Har as-of oy t (oy oxiri): faqat t gacha ma'lum belgilar + t, t+1 natijasi."""
mj = bank["mijozlar"]
ids, kol = mj["mijoz_id"].to_numpy(), np.arange(1, OYLAR + 1)
def mat(df, qiymat, ustun="oy"):
return (df.pivot(index="mijoz_id", columns=ustun, values=qiymat)
.reindex(index=ids, columns=kol).to_numpy(dtype=float))
hv, tl = bank["hisobvaraqlar"], bank["tolovlar"]
bal, mint = mat(hv, "balans"), mat(hv, "min_tolov")
tol, kun = mat(tl, "summa", "hisob_oy"), mat(tl, "tolov_kuni", "hisob_oy")
bor = ~np.isnan(bal)
miss = bor & ~(np.nan_to_num(tol) >= mint - 1e-9) # minimal to'lov qilinmagan
g = (bank["tranzaksiyalar"].groupby(["turi", "mijoz_id", "oy"])["summa"]
.agg(["size", "sum"]))
def tr_mat(turi, ust):
return (g.loc[turi, ust].unstack("oy").reindex(index=ids, columns=kol)
.fillna(0).to_numpy())
naqd_n, naqd_s = tr_mat("naqd", "size"), tr_mat("naqd", "sum")
xarid_n = tr_mat("xarid", "size")
util = bal / mj["limit"].to_numpy()[:, None]
och = mj["ochilgan_oy"].to_numpy()
qismlar = []
for t in oylar:
c = t - 1 # t-oy ustuni (0 dan)
ok = (och <= t - 1) & bor[:, c] & bor[:, c - 1] & ~miss[:, c - 1]
u3 = util[:, c - 3]
f = mj.drop(columns="ochilgan_oy").assign(
oy=t, staj=t - och, util=util[:, c], util_3=orta(util[:, c - 2:c + 1]),
util_ozg=util[:, c] - np.where(np.isnan(u3), util[:, c], u3),
kechikish_6=miss[:, max(c - 6, 0):c].sum(axis=1),
tolov_nisbati_3=orta(np.nan_to_num(tol[:, c - 3:c]) / bal[:, c - 3:c]),
tolov_kuni_3=orta(kun[:, c - 3:c]),
naqd_soni_3=naqd_n[:, c - 2:c + 1].sum(axis=1),
naqd_summa_3=naqd_s[:, c - 2:c + 1].sum(axis=1),
xarid_soni_3=xarid_n[:, c - 2:c + 1].sum(axis=1),
balans=bal[:, c], y30=(miss[:, c] | miss[:, c + 1]).astype(int),
y60=(miss[:, c] & miss[:, c + 1]).astype(int))
qismlar.append(f[ok])
df = pd.concat(qismlar, ignore_index=True)
df["tolov_kuni_3"] = df["tolov_kuni_3"].fillna(25.0)
df["tolov_nisbati_3"] = df["tolov_nisbati_3"].fillna(0.0)
df["zarar"] = 0.45 * df["balans"] # DPD60 bo'lsa kutilgan zarar
df["qiymat"] = SAMARA * df["zarar"] * df["y60"] - NARX # qo'ng'iroq qiymati
return df
SHABLON = """# Kredit karta kechikishi: oylik hisobot ({davr})
**Xulosa.** Model ro'yxati joriy qoidadan oyiga {farq:.1f} mln so'm ko'p tejaydi
(bootstrap SE {farq_se:.1f}; {yutgan} oyning {oylar} tasida yaxshi).
**Tavsiya.** {tavsiya}.
## Asosiy raqamlar
| Ko'rsatkich | Model | Qoida |
|---|---|---|
| Tejam, mln so'm/oy | {tejam_m:.1f} | {tejam_q:.1f} |
| Ro'yxatdagi xavfli mijozlar, % | {prec_m:.1f} | {prec_q:.1f} |
| Oyiga qo'ng'iroq | {k} | {k} |
## Sifat va xatarlar
- AUC {auc:.3f}; bashorat / haqiqiy ulush {kal:.2f}{kal_izoh}
- Qo'ng'iroq samarasi faraziy ({samara:.2f}) - pilotda o'lchanadi
"""
def top_k(d, ball, k=K):
oy = d["oy"].to_numpy()
return np.concatenate([g[np.argsort(-ball[g], kind="stable")[:k]]
for g in [np.flatnonzero(oy == m) for m in np.unique(oy)]])
def natijalar():
"""Butun tahlil: bitta lug'at qaytaradi - hisobotdagi HAR raqam shu yerdan."""
df = belgilar_jadvali(yarat_bank())
oquv, test = df[df.oy <= 18], df[df.oy.between(20, 22)].reset_index(drop=True)
m = make_pipeline(StandardScaler(), LogisticRegression(max_iter=2000))
p = m.fit(oquv[BELGILAR], oquv.y60).predict_proba(test[BELGILAR])[:, 1]
q, y, oy = test.qiymat.to_numpy(), test.y60.to_numpy(), test.oy.to_numpy()
tm, tq = top_k(test, p * test.zarar.to_numpy()), top_k(test, test.util.to_numpy())
oylik_m = np.array([q[tm][oy[tm] == t].sum() for t in range(20, 23)])
oylik_q = np.array([q[tq][oy[tq] == t].sum() for t in range(20, 23)])
d = oylik_m - oylik_q
kal = p.mean() / y.mean()
rng = np.random.default_rng(3) # oy ichida bootstrap (29.3 bilan bir xil)
guruh = [np.flatnonzero(oy == t) for t in range(20, 23)]
bs = []
for _ in range(300):
idx = np.concatenate([rng.choice(g, len(g)) for g in guruh])
sub = test.iloc[idx].reset_index(drop=True)
a = sub.qiymat.to_numpy()[top_k(sub, p[idx] * sub.zarar.to_numpy())].sum()
b = sub.qiymat.to_numpy()[top_k(sub, sub.util.to_numpy())].sum()
bs.append((a - b) / 3)
se = float(np.std(bs, ddof=1))
return {
"davr": f"{oy_nomi(20)} - {oy_nomi(22)}", "oylar": 3, "k": K, "samara": SAMARA,
"tejam_m": oylik_m.mean(), "tejam_q": oylik_q.mean(), "farq": d.mean(),
"farq_se": se, "yutgan": int((d > 0).sum()),
"prec_m": 100 * y[tm].mean(), "prec_q": 100 * y[tq].mean(),
"auc": roc_auc_score(y, p), "kal": kal,
"kal_izoh": " (model xavfni kam baholaydi - qayta kalibrlash)" if kal < 0.9 else "",
"tavsiya": ("Modelni nazorat guruhli pilot bilan ishga tushirish"
if d.mean() > 2 * se
else "Qoidani saqlash, model bo'yicha ko'proq ma'lumot yig'ish"),
}
def kelib_chiqish(matn, r):
"""Hisobotdagi har son natijaga (yoki ruxsat etilgan doimiyga) bog'lanadimi?"""
ruxsat = set()
for v in r.values():
if isinstance(v, (int, float, np.floating, np.integer)):
for fmt in ["{:.0f}", "{:.1f}", "{:.2f}", "{:.3f}", "{}"]:
ruxsat.add(fmt.format(v))
ruxsat |= {"2025", "08", "10"} # sana qismlari
sonlar = re.findall(r"\d+(?:\.\d+)?", matn)
return sorted({s for s in sonlar if s not in ruxsat})
def main() -> None:
r = natijalar()
hisobot = SHABLON.format(**r)
with tempfile.TemporaryDirectory() as papka:
yol = pathlib.Path(papka) / "hisobot.md"
yol.write_text(hisobot, encoding="utf8")
print("=== 1. Yaratilgan hisobot (hisobot.md) ===")
print(yol.read_text(encoding="utf8").rstrip())
print("\n=== 2. Kelib chiqish tekshiruvi: har son natijadanmi? ===")
begona = kelib_chiqish(hisobot, r)
print(f" hisobotdagi sonlar: {len(re.findall(r'[0-9]+(?:[.][0-9]+)?', hisobot))}, "
f"natijaga bog'lanmagan: {begona or 'hech biri'}")
qolda = hisobot.replace(f"{r['tejam_m']:.1f} |", "45 |", 1) # kimdir "yaxlitladi"
print(f" qo'lda tahrir qilingan nusxa: bog'lanmagan {kelib_chiqish(qolda, r)}")
print("\n=== 3. Qayta ishlab chiqarish: ikki marta yaratib solishtirish ===")
x1 = hashlib.sha256(hisobot.encode()).hexdigest()[:12]
x2 = hashlib.sha256(SHABLON.format(**natijalar()).encode()).hexdigest()[:12]
print(f" xesh 1: {x1}, xesh 2: {x2}, bir xil: {x1 == x2}")
print("\n=== 4. Xulosa (natijadan hisoblangan) ===")
if not begona:
print(" hisobotdagi hamma son natija lug'atidan - qo'lda ko'chirilgan raqam yo'q")
if x1 == x2:
print(" hisobot deterministik: ma'lumot o'zgarmasa, hisobot ham o'zgarmaydi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Yaratilgan hisobot (hisobot.md) ===
# Kredit karta kechikishi: oylik hisobot (2025-08 - 2025-10)
**Xulosa.** Model ro'yxati joriy qoidadan oyiga 11.3 mln so'm ko'p tejaydi
(bootstrap SE 4.0; 3 oyning 3 tasida yaxshi).
**Tavsiya.** Modelni nazorat guruhli pilot bilan ishga tushirish.
## Asosiy raqamlar
| Ko'rsatkich | Model | Qoida |
|---|---|---|
| Tejam, mln so'm/oy | 42.3 | 31.1 |
| Ro'yxatdagi xavfli mijozlar, % | 13.5 | 11.7 |
| Oyiga qo'ng'iroq | 200 | 200 |
## Sifat va xatarlar
- AUC 0.830; bashorat / haqiqiy ulush 0.88 (model xavfni kam baholaydi - qayta kalibrlash)
- Qo'ng'iroq samarasi faraziy 0.30-bob - pilotda o'lchanadi
=== 2. Kelib chiqish tekshiruvi: har son natijadanmi? ===
hisobotdagi sonlar: 17, natijaga bog'lanmagan: hech biri
qo'lda tahrir qilingan nusxa: bog'lanmagan ['45']
=== 3. Qayta ishlab chiqarish: ikki marta yaratib solishtirish ===
xesh 1: d243cd2d34c6, xesh 2: d243cd2d34c6, bir xil: True
=== 4. Xulosa (natijadan hisoblangan) ===
hisobotdagi hamma son natija lug'atidan - qo'lda ko'chirilgan raqam yo'q
hisobot deterministik: ma'lumot o'zgarmasa, hisobot ham o'zgarmaydiNatija tahlili.
1-bo'lim — natijalar() funksiyasi butun tahlilni bajaradi va bitta lug'at qaytaradi. Shablon undan markdown hisobot yaratadi. Hisobotdagi xulosa (11.3, bootstrap SE 4.0, 3 oyning 3 tasi), jadval (42.3 va 31.1, 13.5% va 11.7%) va xatarlar bandi natijadan olingan. Tavsiya matni ham shartdan tanlangan (farq > 2*SE). Kalibrlash izohi esa faqat kal < 0.9 bo'lganda qo'shiladi (0.88). SE 29.3 dagi bilan bir xil usulda hisoblangani uchun raqamlar darslar orasida mos keladi.
2-bo'lim — kelib chiqish tekshiruvi. Hisobotdagi 17 ta sonning hammasi natija lug'atidagi biror qiymatning formatiga mos keldi. Keyin "kimdir yaxlitladi" holatini simulyatsiya qildik: jadvaldagi 42.3 qo'lda 45 ga almashtirildi, va tekshiruv uni darhol ushladi (['45']). Amalda bunday tahrirlar yaxshi niyat bilan qilinadi ("chiroyliroq raqam"), lekin keyingi oy hisobot qayta yaratilganda yo'qoladi yoki, undan ham yomoni, qoladi va boshqa raqamlarga zid bo'lib chiqadi.
3-bo'lim — hisobot ikki marta noldan yaratildi va xeshlar bir xil chiqdi (d243cd2d34c6). Demak hisobot deterministik: urug'lar belgilangan, lug'at tartibi va formatlar barqaror. Bu hisobotni versiyalash va "o'tgan oyga nisbatan nima o'zgardi?" savoliga javob berish uchun asos.
Misol 3 — Grafik tamoyillari: yomon va yaxshi grafik, elementlarini avtomatik tekshirish
"""Grafik tamoyillari: yomon va yaxshi grafik, elementlarini avtomatik tekshirish (Agg)."""
import pathlib
import tempfile
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt # noqa: E402
import numpy as np # noqa: E402
import pandas as pd # noqa: E402
from sklearn.linear_model import LogisticRegression # noqa: E402
from sklearn.pipeline import make_pipeline # noqa: E402
from sklearn.preprocessing import StandardScaler # noqa: E402
HUDUDLAR = ["Toshkent", "Samarqand", "Farg'ona", "Andijon", "Buxoro", "Qashqadaryo"]
OYLAR = 24 # 1 = 2024-01, ..., 24 = 2025-12
BELGILAR = ["yosh", "daromad", "limit", "avto_tolov", "maosh_loyihasi", "staj",
"util", "util_3", "util_ozg", "kechikish_6", "tolov_nisbati_3",
"tolov_kuni_3", "naqd_soni_3", "naqd_summa_3", "xarid_soni_3"]
K, SAMARA, NARX = 200, 0.30, 0.02 # oyiga qo'ng'iroq, oldini olish, mln so'm
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def oy_nomi(m):
return f"{2024 + (m - 1) // 12}-{(m - 1) % 12 + 1:02d}"
def yarat_bank(seed=29, n=6000):
"""Kredit karta portfeli: mijozlar, hisobvaraqlar, tolovlar, tranzaksiyalar."""
rng = np.random.default_rng(seed)
ids = np.arange(1, n + 1)
yosh = rng.integers(21, 66, n)
maosh = rng.random(n) < 0.45
daromad = np.round(np.exp(rng.normal(np.log(6.0), 0.45, n)), 2) # mln so'm
ochilgan = np.where(rng.random(n) < 0.7, rng.integers(-40, 1, n),
rng.integers(1, 20, n))
xavf = (rng.normal(0, 1, n) - 0.5 * maosh - 0.8 * np.log(daromad / 6)
+ 0.015 * (35 - yosh)) # yashirin: moliyaviy xavf
unut = rng.normal(0, 1, n) + 0.03 * (35 - yosh) # yashirin: unutuvchanlik
avto = (rng.random(n) < sigmoid(-0.2 - 0.9 * unut)).astype(int)
limit = np.round(daromad * rng.uniform(1.5, 3.5, n), 1)
hudud = rng.choice(HUDUDLAR, n, p=[0.34, 0.15, 0.14, 0.13, 0.12, 0.12])
mj = pd.DataFrame({"mijoz_id": ids, "yosh": yosh, "hudud": hudud,
"daromad": daromad, "limit": limit, "avto_tolov": avto,
"maosh_loyihasi": maosh.astype(int), "ochilgan_oy": ochilgan})
s, oldingi, ketma = rng.normal(0, 1, n), np.zeros(n, int), np.zeros(n, int)
hv, tl, tr = [], [], []
for m in range(1, OYLAR + 1):
faol = (ochilgan <= m) & (ketma < 3) # 3 ketma-ket kechikish - defolt
s = 0.75 * s + 0.66 * rng.normal(0, 1, n)
d = 0.8 * xavf + 0.7 * s + 0.15 * (m % 12 == 1) + 0.08 * max(m - 18, 0)
util = np.clip(sigmoid(-0.4 + 0.55 * d + rng.normal(0, 0.5, n)), 0.01, 1)
balans = np.round(util * limit, 3)
min_t = np.round(np.maximum(0.05 * balans, 0.05), 3)
for turi, lam in [("xarid", np.exp(1.2 + 0.15 * np.log(daromad / 6) - 0.1 * d)),
("naqd", np.exp(-1.6 + 0.6 * d)),
("onlayn", np.exp(0.6 - 0.02 * (yosh - 35)))]:
i = np.repeat(np.arange(n), rng.poisson(lam) * faol)
summa = (rng.uniform(0.2, 1.5, i.size) * (1 + 0.3 * d[i].clip(0))
if turi == "naqd" else np.exp(rng.normal(np.log(0.25), 0.8, i.size)))
tr.append(pd.DataFrame({"mijoz_id": ids[i], "oy": m,
"kun": rng.integers(1, 29, i.size), "turi": turi,
"summa": np.round(summa, 3)}))
q = rng.random(n) < sigmoid(-3.7 + 1.3 * d + 1.2 * (util > 0.85)
+ 3.8 * (oldingi == 2)) # qiyinchilik
u = ~q & (rng.random(n) < sigmoid(-3.4 + 1.1 * unut - 1.8 * avto)
* (oldingi != 1)) # unutish
miss = (q | u) & faol
ulush = sigmoid(0.3 - 1.2 * d + rng.normal(0, 0.5, n))
summa = np.where(miss, min_t * rng.uniform(0, 0.6, n),
min_t + (balans - min_t).clip(0) * ulush)
tolaydi = faol & (~miss | (rng.random(n) < 0.4))
kun = np.clip(np.round(6 + 14 * sigmoid(unut + 0.5 * d)
+ rng.normal(0, 3, n)), 1, 25)
hv.append(pd.DataFrame({"mijoz_id": ids[faol], "oy": m,
"balans": balans[faol], "min_tolov": min_t[faol]}))
if m < OYLAR: # m-oy hisobvarag'i (m+1)-oyda to'lanadi
tl.append(pd.DataFrame({"mijoz_id": ids[tolaydi], "hisob_oy": m,
"tolov_oy": m + 1,
"tolov_kuni": kun[tolaydi].astype(int),
"summa": np.round(summa[tolaydi], 3)}))
ketma = np.where(faol, np.where(miss, ketma + 1, 0), ketma)
oldingi = np.where(miss, np.where(q, 2, 1), 0)
return {"mijozlar": mj, "hisobvaraqlar": pd.concat(hv, ignore_index=True),
"tolovlar": pd.concat(tl, ignore_index=True),
"tranzaksiyalar": pd.concat(tr, ignore_index=True)}
def orta(a):
"""Qator bo'yicha NaN siz o'rtacha (bo'sh qatorda NaN, ogohlantirishsiz)."""
k = (~np.isnan(a)).sum(axis=1)
return np.where(k > 0, np.nansum(a, axis=1) / np.maximum(k, 1), np.nan)
def belgilar_jadvali(bank, oylar=range(7, 23)):
"""Har as-of oy t (oy oxiri): faqat t gacha ma'lum belgilar + t, t+1 natijasi."""
mj = bank["mijozlar"]
ids, kol = mj["mijoz_id"].to_numpy(), np.arange(1, OYLAR + 1)
def mat(df, qiymat, ustun="oy"):
return (df.pivot(index="mijoz_id", columns=ustun, values=qiymat)
.reindex(index=ids, columns=kol).to_numpy(dtype=float))
hv, tl = bank["hisobvaraqlar"], bank["tolovlar"]
bal, mint = mat(hv, "balans"), mat(hv, "min_tolov")
tol, kun = mat(tl, "summa", "hisob_oy"), mat(tl, "tolov_kuni", "hisob_oy")
bor = ~np.isnan(bal)
miss = bor & ~(np.nan_to_num(tol) >= mint - 1e-9) # minimal to'lov qilinmagan
g = (bank["tranzaksiyalar"].groupby(["turi", "mijoz_id", "oy"])["summa"]
.agg(["size", "sum"]))
def tr_mat(turi, ust):
return (g.loc[turi, ust].unstack("oy").reindex(index=ids, columns=kol)
.fillna(0).to_numpy())
naqd_n, naqd_s = tr_mat("naqd", "size"), tr_mat("naqd", "sum")
xarid_n = tr_mat("xarid", "size")
util = bal / mj["limit"].to_numpy()[:, None]
och = mj["ochilgan_oy"].to_numpy()
qismlar = []
for t in oylar:
c = t - 1 # t-oy ustuni (0 dan)
ok = (och <= t - 1) & bor[:, c] & bor[:, c - 1] & ~miss[:, c - 1]
u3 = util[:, c - 3]
f = mj.drop(columns="ochilgan_oy").assign(
oy=t, staj=t - och, util=util[:, c], util_3=orta(util[:, c - 2:c + 1]),
util_ozg=util[:, c] - np.where(np.isnan(u3), util[:, c], u3),
kechikish_6=miss[:, max(c - 6, 0):c].sum(axis=1),
tolov_nisbati_3=orta(np.nan_to_num(tol[:, c - 3:c]) / bal[:, c - 3:c]),
tolov_kuni_3=orta(kun[:, c - 3:c]),
naqd_soni_3=naqd_n[:, c - 2:c + 1].sum(axis=1),
naqd_summa_3=naqd_s[:, c - 2:c + 1].sum(axis=1),
xarid_soni_3=xarid_n[:, c - 2:c + 1].sum(axis=1),
balans=bal[:, c], y30=(miss[:, c] | miss[:, c + 1]).astype(int),
y60=(miss[:, c] & miss[:, c + 1]).astype(int))
qismlar.append(f[ok])
df = pd.concat(qismlar, ignore_index=True)
df["tolov_kuni_3"] = df["tolov_kuni_3"].fillna(25.0)
df["tolov_nisbati_3"] = df["tolov_nisbati_3"].fillna(0.0)
df["zarar"] = 0.45 * df["balans"] # DPD60 bo'lsa kutilgan zarar
df["qiymat"] = SAMARA * df["zarar"] * df["y60"] - NARX # qo'ng'iroq qiymati
return df
def top_k(d, ball, k=K):
oy = d["oy"].to_numpy()
return np.concatenate([g[np.argsort(-ball[g], kind="stable")[:k]]
for g in [np.flatnonzero(oy == m) for m in np.unique(oy)]])
def lr_p(tr, te):
m = make_pipeline(StandardScaler(), LogisticRegression(max_iter=2000))
return m.fit(tr[BELGILAR], tr.y60).predict_proba(te[BELGILAR])[:, 1]
def tekshir(fig):
"""Grafik elementlarini sanaydi va tamoyillar buzilishini qaytaradi."""
buzilish, satr = [], []
for i, ax in enumerate(fig.axes):
chiziq = [c for c in ax.lines if not c.get_label().startswith("_")]
ustun = [p for p in ax.patches if isinstance(p, matplotlib.patches.Rectangle)]
leg = ax.get_legend()
n_leg = len(leg.get_texts()) if leg else 0
ranglar = {matplotlib.colors.to_hex(c.get_color()) for c in ax.lines}
satr.append(f"ax{i}: chiziq {len(ax.lines)}, ustun {len(ustun)}, matn {len(ax.texts)}, "
f"legenda {n_leg}, rang {len(ranglar)}")
if ustun:
past = ax.get_ylim()[0]
h = sorted(p.get_height() for p in ustun)
yolgon = ((h[-1] - past) / (h[0] - past)) / (h[-1] / h[0])
if past > 0:
buzilish.append(f"ax{i}: ustun o'qi {past:.0f} dan boshlanadi, "
f"yolg'on koeffitsienti {yolgon:.1f}")
if len(ax.lines) + n_leg > 8 or len(chiziq) > 4:
buzilish.append(f"ax{i}: {max(len(chiziq), len(ax.lines))} qator - spagetti")
egizak = [j for j, b in enumerate(fig.axes)
if j != i and ax.get_shared_x_axes().joined(ax, b)]
if egizak and i < egizak[0]:
buzilish.append(f"ax{i}-ax{egizak[0]}: ikkinchi y o'qi (twinx)")
if egizak and i > egizak[0]:
continue # egizak o'qning sarlavhasi yo'q - normal
sarlavha = ax.get_title()
if len(sarlavha.split()) < 5:
buzilish.append(f"ax{i}: sarlavha xulosa emas ('{sarlavha}')")
return satr, buzilish
def main() -> None:
df = belgilar_jadvali(yarat_bank())
tr, va = df[df.oy <= 14], df[df.oy.between(16, 18)].reset_index(drop=True)
p = lr_p(tr, va)
ev = p * SAMARA * va.zarar.to_numpy() - NARX
kl = [100, 200, 300, 400, 600, 800, 1000]
kutilgan = [ev[top_k(va, p * va.zarar.to_numpy(), k)].sum() / 3 for k in kl]
oquv, test = df[df.oy <= 18], df[df.oy.between(20, 22)].reset_index(drop=True)
pt = lr_p(oquv, test)
q = test.qiymat.to_numpy()
model = q[top_k(test, pt * test.zarar.to_numpy())].sum() / 3
qoida = q[top_k(test, test.util.to_numpy())].sum() / 3
# YOMON: kesilgan o'q, 6 qator, ikkinchi o'q, legenda, "Grafik 1"
fig1, (a1, a2) = plt.subplots(1, 2, figsize=(10, 4))
a1.bar(["Model", "Qoida"], [model, qoida], color=["tab:red", "tab:green"])
a1.set_ylim(30, 43)
a1.set_title("Grafik 1")
for j, nom in enumerate(["tejam", "kutilgan", "AUC", "precision", "recall", "Brier"]):
a2.plot(kl, np.array(kutilgan) * (1 + 0.1 * j), label=nom)
a2.legend()
a2b = a2.twinx()
a2b.plot(kl, np.array(kl) / 1000, color="black", label="ulush")
a2.set_title("Natijalar")
# YAXSHI: 0 dan boshlangan ustunlar, qiymat yozuvlari, bitta chiziq, xulosa-sarlavha
fig2, (b1, b2) = plt.subplots(1, 2, figsize=(10, 4))
b1.bar(["Model", "Joriy qoida"], [model, qoida], color=["#1f77b4", "#bbbbbb"])
for x, v in enumerate([model, qoida]):
b1.text(x, v + 1, f"{v:.0f}", ha="center")
b1.set_ylim(0, model * 1.2)
b1.set_ylabel("tejam, mln so'm/oy")
b1.set_title(f"Model oyiga ~{model - qoida:.0f} mln so'm ko'p tejaydi")
b2.plot(kl, kutilgan, marker="o", color="#1f77b4")
b2.axvline(K, color="#bbbbbb", linestyle="--")
i200, i300 = kl.index(200), kl.index(300)
b2.annotate(f"+100 qo'ng'iroq: +{kutilgan[i300] - kutilgan[i200]:.1f} mln",
(300, kutilgan[i300]), (420, kutilgan[i200]))
b2.set_ylim(0, max(kutilgan) * 1.15)
b2.set_xlabel("oyiga qo'ng'iroq (K)")
b2.set_title("Har keyingi 100 qo'ng'iroq kamroq qo'shadi")
with tempfile.TemporaryDirectory() as papka:
for nom, fig in [("yomon", fig1), ("yaxshi", fig2)]:
yol = pathlib.Path(papka) / f"{nom}.png"
fig.savefig(yol, dpi=80)
satr, buz = tekshir(fig)
print(f"=== {nom.upper()} grafik ({yol.name}, saqlandi: {yol.exists()}) ===")
for s in satr:
print(" " + s)
print(f" buzilishlar ({len(buz)}):")
for b in buz:
print(" - " + b)
if not buz:
print(" - yo'q")
print()
plt.close(fig)
print("=== Xulosa (natijadan hisoblangan) ===")
haqiqiy = model / qoida
vizual = (model - 30) / (qoida - 30)
print(f" ma'lumotdagi nisbat {haqiqiy:.2f}, kesilgan o'qdagi ko'rinish {vizual:.1f} "
f"barobar -> farq {vizual / haqiqiy:.1f} marta bo'rttirilgan")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== YOMON grafik (yomon.png, saqlandi: True) ===
ax0: chiziq 0, ustun 2, matn 0, legenda 0, rang 0
ax1: chiziq 6, ustun 0, matn 0, legenda 6, rang 6
ax2: chiziq 1, ustun 0, matn 0, legenda 0, rang 1
buzilishlar (5):
- ax0: ustun o'qi 30 dan boshlanadi, yolg'on koeffitsienti 8.4
- ax0: sarlavha xulosa emas ('Grafik 1')
- ax1: 6 qator - spagetti
- ax1-ax2: ikkinchi y o'qi (twinx)
- ax1: sarlavha xulosa emas ('Natijalar')
=== YAXSHI grafik (yaxshi.png, saqlandi: True) ===
ax0: chiziq 0, ustun 2, matn 2, legenda 0, rang 0
ax1: chiziq 2, ustun 0, matn 1, legenda 0, rang 2
buzilishlar (0):
- yo'q
=== Xulosa (natijadan hisoblangan) ===
ma'lumotdagi nisbat 1.36, kesilgan o'qdagi ko'rinish 11.5 barobar -> farq 8.4 marta bo'rttirilganNatija tahlili.
Yomon grafik — taqdimotlarda tez-tez uchraydigan to'plam: kesilgan o'qli ustun, oltita chiziq, legenda, ikkinchi y o'qi va "Grafik 1" / "Natijalar" sarlavhalari. Tekshiruv 5 ta buzilish topdi:
- Kesilgan o'q — eng xavflisi. Ma'lumotda model qoidadan
1.36barobar ko'p tejaydi, o'q 30 dan boshlangani uchun esa grafikda11.5barobar ko'rinadi. Yolg'on koeffitsienti8.4. 29.4 kirishidagi yig'ilishda direktor aynan shunday grafikdan "model 10 barobar yaxshi" degan taassurot olishi mumkin edi. - Oltita chiziq — spagetti.
- Ikkinchi o'q (
ax1-ax2) — ikki xil shkala, o'quvchi qaysi chiziq qaysi o'qqa tegishli ekanini taxmin qiladi. - Ikki sarlavha — xulosa emas.
Yaxshi grafik — 0 dan boshlangan ikki ustun va ularning ustida qiymat yozuvlari. Sarlavha xulosa: "Model oyiga ~11 mln so'm ko'p tejaydi". Ikkinchi panelda bitta sig'im egri chizig'i, joriy K belgisi va to'g'ridan-to'g'ri izoh bor: "+100 qo'ng'iroq: +5.8 mln". Sarlavha: "Har keyingi 100 qo'ng'iroq kamroq qo'shadi". Buzilishlar 0.
Tekshiruv fayl hajmiga emas, matplotlib obyektlariga qaraydi: chiziqlar, ustunlar, matnlar, legenda, o'q chegaralari, egizak o'qlar. Bu tekshiruvni hisobot generatoriga (2-misol) qo'shish mumkin: grafik tamoyillarni buzsa, hisobot yaratilmaydi. Ikkala PNG fayl vaqtinchalik papkaga saqlandi, ular faqat nomi bilan chop etildi.
Misol 4 — Nima qilmaslik kerak: cherry-picking va p-hacking; pilot hajmi va quvvati
"""Nima qilmaslik kerak: cherry-picking va p-hacking optimizmi; pilot (A/B) hajmi va quvvati."""
import math
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
HUDUDLAR = ["Toshkent", "Samarqand", "Farg'ona", "Andijon", "Buxoro", "Qashqadaryo"]
OYLAR = 24 # 1 = 2024-01, ..., 24 = 2025-12
BELGILAR = ["yosh", "daromad", "limit", "avto_tolov", "maosh_loyihasi", "staj",
"util", "util_3", "util_ozg", "kechikish_6", "tolov_nisbati_3",
"tolov_kuni_3", "naqd_soni_3", "naqd_summa_3", "xarid_soni_3"]
K, SAMARA, NARX = 200, 0.30, 0.02 # oyiga qo'ng'iroq, oldini olish, mln so'm
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def oy_nomi(m):
return f"{2024 + (m - 1) // 12}-{(m - 1) % 12 + 1:02d}"
def yarat_bank(seed=29, n=6000):
"""Kredit karta portfeli: mijozlar, hisobvaraqlar, tolovlar, tranzaksiyalar."""
rng = np.random.default_rng(seed)
ids = np.arange(1, n + 1)
yosh = rng.integers(21, 66, n)
maosh = rng.random(n) < 0.45
daromad = np.round(np.exp(rng.normal(np.log(6.0), 0.45, n)), 2) # mln so'm
ochilgan = np.where(rng.random(n) < 0.7, rng.integers(-40, 1, n),
rng.integers(1, 20, n))
xavf = (rng.normal(0, 1, n) - 0.5 * maosh - 0.8 * np.log(daromad / 6)
+ 0.015 * (35 - yosh)) # yashirin: moliyaviy xavf
unut = rng.normal(0, 1, n) + 0.03 * (35 - yosh) # yashirin: unutuvchanlik
avto = (rng.random(n) < sigmoid(-0.2 - 0.9 * unut)).astype(int)
limit = np.round(daromad * rng.uniform(1.5, 3.5, n), 1)
hudud = rng.choice(HUDUDLAR, n, p=[0.34, 0.15, 0.14, 0.13, 0.12, 0.12])
mj = pd.DataFrame({"mijoz_id": ids, "yosh": yosh, "hudud": hudud,
"daromad": daromad, "limit": limit, "avto_tolov": avto,
"maosh_loyihasi": maosh.astype(int), "ochilgan_oy": ochilgan})
s, oldingi, ketma = rng.normal(0, 1, n), np.zeros(n, int), np.zeros(n, int)
hv, tl, tr = [], [], []
for m in range(1, OYLAR + 1):
faol = (ochilgan <= m) & (ketma < 3) # 3 ketma-ket kechikish - defolt
s = 0.75 * s + 0.66 * rng.normal(0, 1, n)
d = 0.8 * xavf + 0.7 * s + 0.15 * (m % 12 == 1) + 0.08 * max(m - 18, 0)
util = np.clip(sigmoid(-0.4 + 0.55 * d + rng.normal(0, 0.5, n)), 0.01, 1)
balans = np.round(util * limit, 3)
min_t = np.round(np.maximum(0.05 * balans, 0.05), 3)
for turi, lam in [("xarid", np.exp(1.2 + 0.15 * np.log(daromad / 6) - 0.1 * d)),
("naqd", np.exp(-1.6 + 0.6 * d)),
("onlayn", np.exp(0.6 - 0.02 * (yosh - 35)))]:
i = np.repeat(np.arange(n), rng.poisson(lam) * faol)
summa = (rng.uniform(0.2, 1.5, i.size) * (1 + 0.3 * d[i].clip(0))
if turi == "naqd" else np.exp(rng.normal(np.log(0.25), 0.8, i.size)))
tr.append(pd.DataFrame({"mijoz_id": ids[i], "oy": m,
"kun": rng.integers(1, 29, i.size), "turi": turi,
"summa": np.round(summa, 3)}))
q = rng.random(n) < sigmoid(-3.7 + 1.3 * d + 1.2 * (util > 0.85)
+ 3.8 * (oldingi == 2)) # qiyinchilik
u = ~q & (rng.random(n) < sigmoid(-3.4 + 1.1 * unut - 1.8 * avto)
* (oldingi != 1)) # unutish
miss = (q | u) & faol
ulush = sigmoid(0.3 - 1.2 * d + rng.normal(0, 0.5, n))
summa = np.where(miss, min_t * rng.uniform(0, 0.6, n),
min_t + (balans - min_t).clip(0) * ulush)
tolaydi = faol & (~miss | (rng.random(n) < 0.4))
kun = np.clip(np.round(6 + 14 * sigmoid(unut + 0.5 * d)
+ rng.normal(0, 3, n)), 1, 25)
hv.append(pd.DataFrame({"mijoz_id": ids[faol], "oy": m,
"balans": balans[faol], "min_tolov": min_t[faol]}))
if m < OYLAR: # m-oy hisobvarag'i (m+1)-oyda to'lanadi
tl.append(pd.DataFrame({"mijoz_id": ids[tolaydi], "hisob_oy": m,
"tolov_oy": m + 1,
"tolov_kuni": kun[tolaydi].astype(int),
"summa": np.round(summa[tolaydi], 3)}))
ketma = np.where(faol, np.where(miss, ketma + 1, 0), ketma)
oldingi = np.where(miss, np.where(q, 2, 1), 0)
return {"mijozlar": mj, "hisobvaraqlar": pd.concat(hv, ignore_index=True),
"tolovlar": pd.concat(tl, ignore_index=True),
"tranzaksiyalar": pd.concat(tr, ignore_index=True)}
def orta(a):
"""Qator bo'yicha NaN siz o'rtacha (bo'sh qatorda NaN, ogohlantirishsiz)."""
k = (~np.isnan(a)).sum(axis=1)
return np.where(k > 0, np.nansum(a, axis=1) / np.maximum(k, 1), np.nan)
def belgilar_jadvali(bank, oylar=range(7, 23)):
"""Har as-of oy t (oy oxiri): faqat t gacha ma'lum belgilar + t, t+1 natijasi."""
mj = bank["mijozlar"]
ids, kol = mj["mijoz_id"].to_numpy(), np.arange(1, OYLAR + 1)
def mat(df, qiymat, ustun="oy"):
return (df.pivot(index="mijoz_id", columns=ustun, values=qiymat)
.reindex(index=ids, columns=kol).to_numpy(dtype=float))
hv, tl = bank["hisobvaraqlar"], bank["tolovlar"]
bal, mint = mat(hv, "balans"), mat(hv, "min_tolov")
tol, kun = mat(tl, "summa", "hisob_oy"), mat(tl, "tolov_kuni", "hisob_oy")
bor = ~np.isnan(bal)
miss = bor & ~(np.nan_to_num(tol) >= mint - 1e-9) # minimal to'lov qilinmagan
g = (bank["tranzaksiyalar"].groupby(["turi", "mijoz_id", "oy"])["summa"]
.agg(["size", "sum"]))
def tr_mat(turi, ust):
return (g.loc[turi, ust].unstack("oy").reindex(index=ids, columns=kol)
.fillna(0).to_numpy())
naqd_n, naqd_s = tr_mat("naqd", "size"), tr_mat("naqd", "sum")
xarid_n = tr_mat("xarid", "size")
util = bal / mj["limit"].to_numpy()[:, None]
och = mj["ochilgan_oy"].to_numpy()
qismlar = []
for t in oylar:
c = t - 1 # t-oy ustuni (0 dan)
ok = (och <= t - 1) & bor[:, c] & bor[:, c - 1] & ~miss[:, c - 1]
u3 = util[:, c - 3]
f = mj.drop(columns="ochilgan_oy").assign(
oy=t, staj=t - och, util=util[:, c], util_3=orta(util[:, c - 2:c + 1]),
util_ozg=util[:, c] - np.where(np.isnan(u3), util[:, c], u3),
kechikish_6=miss[:, max(c - 6, 0):c].sum(axis=1),
tolov_nisbati_3=orta(np.nan_to_num(tol[:, c - 3:c]) / bal[:, c - 3:c]),
tolov_kuni_3=orta(kun[:, c - 3:c]),
naqd_soni_3=naqd_n[:, c - 2:c + 1].sum(axis=1),
naqd_summa_3=naqd_s[:, c - 2:c + 1].sum(axis=1),
xarid_soni_3=xarid_n[:, c - 2:c + 1].sum(axis=1),
balans=bal[:, c], y30=(miss[:, c] | miss[:, c + 1]).astype(int),
y60=(miss[:, c] & miss[:, c + 1]).astype(int))
qismlar.append(f[ok])
df = pd.concat(qismlar, ignore_index=True)
df["tolov_kuni_3"] = df["tolov_kuni_3"].fillna(25.0)
df["tolov_nisbati_3"] = df["tolov_nisbati_3"].fillna(0.0)
df["zarar"] = 0.45 * df["balans"] # DPD60 bo'lsa kutilgan zarar
df["qiymat"] = SAMARA * df["zarar"] * df["y60"] - NARX # qo'ng'iroq qiymati
return df
def z_p(k1, n1, k0, n0):
"""Ikki ulush farqi uchun z-test, ikki tomonlama p-qiymat 11.4-bob."""
p = (k1 + k0) / (n1 + n0)
se = np.sqrt(p * (1 - p) * (1 / n1 + 1 / n0))
z = (k1 / n1 - k0 / n0) / np.where(se > 0, se, 1)
return np.vectorize(lambda v: math.erfc(abs(v) / math.sqrt(2)))(z)
def main() -> None:
df = belgilar_jadvali(yarat_bank())
oquv, test = df[df.oy <= 18], df[df.oy.between(20, 22)].reset_index(drop=True)
m = make_pipeline(StandardScaler(), LogisticRegression(max_iter=2000))
p = m.fit(oquv[BELGILAR], oquv.y60).predict_proba(test[BELGILAR])[:, 1]
q, z, y = test.qiymat.to_numpy(), test.zarar.to_numpy(), test.y60.to_numpy()
ball_m, ball_q = p * z, test.util.to_numpy()
oy = test.oy.to_numpy()
print("=== 1. Cherry-picking: 12 ta tasodifiy \"segment\" (haqiqiy farq yo'q) ===")
k_s = round(K / 12)
def ustunlik(maska, oylar):
"""Segmentda model - qoida, 100 qo'ng'iroqqa (mln so'm)."""
jami, n = 0.0, 0
for t in oylar:
g = np.flatnonzero(maska & (oy == t))
jami += (q[g[np.argsort(-ball_m[g], kind="stable")[:k_s]]].sum()
- q[g[np.argsort(-ball_q[g], kind="stable")[:k_s]]].sum())
n += k_s
return 100 * jami / n
rng = np.random.default_rng(0)
eng, keyin, orta20, orta22 = [], [], [], []
for _ in range(200):
seg = rng.integers(0, 12, len(test)) # tasodifiy: hammasi bir xil
tanlov = [ustunlik(seg == s, [20, 21]) for s in range(12)]
b = int(np.argmax(tanlov))
eng.append(tanlov[b])
orta20.append(np.mean(tanlov))
yangi = [ustunlik(seg == s, [22]) for s in range(12)]
keyin.append(yangi[b])
orta22.append(np.mean(yangi))
eng, keyin = np.array(eng), np.array(keyin)
umumiy, umumiy22 = np.mean(orta20), np.mean(orta22)
print(f" model - qoida, mln so'm / 100 qo'ng'iroq (har segmentda top-{k_s}):")
print(f" 20-21 oylar: segment o'rtachasi {umumiy:+.2f}, 12 tadan ENG YAXSHISI "
f"{eng.mean():+.2f}")
print(f" eng yaxshisi o'rtachadan 2 barobar katta: {np.mean(eng > 2 * umumiy):.0%} "
f"holatda")
print(f" 22-oy (yangi ma'lumot): o'sha 'eng yaxshi' segment {keyin.mean():+.2f}, "
f"segment o'rtachasi {umumiy22:+.2f}")
print("\n=== 2. p-hacking: A/A da 20 ta kesim, har biri p < 0.05 testi (5000 marta) ===")
rng = np.random.default_rng(1)
n, p0, kesim, takror = 500, 0.12, 20, 5000
k1 = rng.binomial(n, p0, (takror, kesim))
k0 = rng.binomial(n, p0, (takror, kesim))
pq = z_p(k1, n, k0, n)
print(f" bitta oldindan tanlangan test: yolg'on 'topilma' {np.mean(pq[:, 0] < 0.05):.3f}")
print(f" 20 tadan kamida bittasi: {np.mean(pq.min(axis=1) < 0.05):.3f} "
f"(nazariya 1 - 0.95^20 = {1 - 0.95 ** 20:.3f})")
print(f" Bonferroni (0.05 / 20): {np.mean(pq.min(axis=1) < 0.05 / kesim):.3f}")
print("\n=== 3. Pilot dizayni: top-400 dan tasodifan 200 ga qo'ng'iroq, 200 - nazorat ===")
top400 = np.concatenate([g[np.argsort(-ball_m[g], kind="stable")[:400]]
for g in [np.flatnonzero(oy == t) for t in (20, 21, 22)]])
top200 = np.concatenate([g[np.argsort(-ball_m[g], kind="stable")[:200]]
for g in [np.flatnonzero(oy == t) for t in (20, 21, 22)]])
p_naz = y[top400].mean()
za, zb = 1.959964, 0.841621 # alfa 0.05 (ikki tomonlama), quvvat 0.8
rejalar = {}
for samara in [0.30, 0.15]:
p_qon = p_naz * (1 - samara)
pb = (p_naz + p_qon) / 2
n_q = ((za * math.sqrt(2 * pb * (1 - pb)) + zb * math.sqrt(
p_naz * (1 - p_naz) + p_qon * (1 - p_qon))) / (p_naz - p_qon)) ** 2
rejalar[samara] = (p_qon, math.ceil(n_q), math.ceil(n_q / 200))
print(f" samara {samara:.2f}: DPD60 {p_naz:.3f} -> {p_qon:.3f}; har guruhda "
f"{math.ceil(n_q)} mijoz -> {math.ceil(n_q / 200)} oy")
yoqotish = (q[top200].sum() - 0.5 * q[top400].sum()) / 3
print(f" pilot narxi (top-200 o'rniga tasodifiy yarim top-400): ~{yoqotish:.1f} "
f"mln so'm/oy")
print("\n=== 4. Quvvatni simulyatsiya bilan tekshirish (2000 pilot) ===")
rng = np.random.default_rng(2)
for samara, (p_qon, n_q, oylar) in rejalar.items():
nn = oylar * 200
a = rng.binomial(nn, p_qon, 2000)
b = rng.binomial(nn, p_naz, 2000)
quvvat = np.mean((z_p(a, nn, b, nn) < 0.05) & (a < b))
print(f" samara {samara:.2f}, {oylar} oy ({nn} + {nn}): quvvat {quvvat:.3f}")
a = rng.binomial(600, rejalar[0.30][0], 2000)
b = rng.binomial(600, p_naz, 2000)
print(f" taqqoslash - 3 oylik pilot (600 + 600), samara 0.30: quvvat "
f"{np.mean((z_p(a, 600, b, 600) < 0.05) & (a < b)):.3f}")
print("\n=== 5. Xulosa (natijadan hisoblangan) ===")
print(f" 'eng yaxshi segment' o'rtachadan {eng.mean() / umumiy:.1f} barobar yuqori ko'rindi")
if abs(keyin.mean() - umumiy22) < 0.25 * (eng.mean() - umumiy):
print(f" yangi ma'lumotda u o'rtachaga qaytdi ({keyin.mean():+.2f} va {umumiy22:+.2f})"
f" - 'topilma' tanlash effekti edi")
oy30 = rejalar[0.30][2]
print(f" samara 0.30 ni ishonchli ko'rish uchun pilot ~{oy30} oy; 3 oy yetarli emas")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Cherry-picking: 12 ta tasodifiy "segment" (haqiqiy farq yo'q) ===
model - qoida, mln so'm / 100 qo'ng'iroq (har segmentda top-17):
20-21 oylar: segment o'rtachasi +5.63, 12 tadan ENG YAXSHISI +19.47
eng yaxshisi o'rtachadan 2 barobar katta: 100% holatda
22-oy (yangi ma'lumot): o'sha 'eng yaxshi' segment +3.32, segment o'rtachasi +2.53
=== 2. p-hacking: A/A da 20 ta kesim, har biri p < 0.05 testi (5000 marta) ===
bitta oldindan tanlangan test: yolg'on 'topilma' 0.050
20 tadan kamida bittasi: 0.644 (nazariya 1 - 0.95^20 = 0.642)
Bonferroni (0.05 / 20): 0.046
=== 3. Pilot dizayni: top-400 dan tasodifan 200 ga qo'ng'iroq, 200 - nazorat ===
samara 0.30: DPD60 0.113 -> 0.079; har guruhda 1191 mijoz -> 6 oy
samara 0.15: DPD60 0.113 -> 0.096; har guruhda 5139 mijoz -> 26 oy
pilot narxi (top-200 o'rniga tasodifiy yarim top-400): ~11.5 mln so'm/oy
=== 4. Quvvatni simulyatsiya bilan tekshirish (2000 pilot) ===
samara 0.30, 6 oy (1200 + 1200): quvvat 0.805
samara 0.15, 26 oy (5200 + 5200): quvvat 0.805
taqqoslash - 3 oylik pilot (600 + 600), samara 0.30: quvvat 0.517
=== 5. Xulosa (natijadan hisoblangan) ===
'eng yaxshi segment' o'rtachadan 3.5 barobar yuqori ko'rindi
yangi ma'lumotda u o'rtachaga qaytdi (+3.32 va +2.53) - 'topilma' tanlash effekti edi
samara 0.30 ni ishonchli ko'rish uchun pilot ~6 oy; 3 oy yetarli emasNatija tahlili.
1-bo'lim — cherry-picking, haqiqiy loyiha ma'lumotida. Test mijozlarini 12 ta tasodifiy "segment" ga ajratdik. Ular orasida haqiqiy farq yo'q, har biri butun portfelning tasodifiy bo'lagi. 200 marta takrorlaganda o'rtacha segmentda model qoidadan 100 qo'ng'iroq uchun +5.63 mln so'm ko'p tejaydi. 12 tadan eng yaxshisi esa +19.47 ko'rsatadi, 3.5 barobar ko'p, va bu har safar (100%) o'rtachadan ikki barobardan katta. "Toshkentda model 2 barobar yaxshi" degan gap shu mexanizmdan kelib chiqadi. Yangi ma'lumotda (22-oy) o'sha "eng yaxshi" segment +3.32 berdi, segmentlar o'rtachasi esa +2.53: u o'rtachaga qaytdi. "Topilma" tanlashning mahsuli edi. 22-oyda umumiy ustunlik 20-21 dagidan kichik: bu 29.3 dagi oylik farqlarga mos.
2-bo'lim — p-hacking. Haqiqiy farq yo'q (A/A). Bitta oldindan tanlangan test 0.050 holatda "topilma" beradi, bu to'g'ri. 20 ta kesimdan kamida bittasi esa 0.644 holatda "topilma" beradi (nazariya 0.642). Bonferroni tuzatishi buni 0.046 ga tushiradi 11.9-bob. Amaliy qoida: hisobotda nechta kesim tekshirilgani aytiladi, va yangi "topilma" yangi ma'lumotda tasdiqlanmaguncha gipoteza deb ataladi.
3-bo'lim — pilot hajmi. Top-400 da DPD60 ulushi 0.113. Samara 0.30 bo'lsa, qo'ng'iroq guruhida 0.079 kutiladi. Buni alfa 0.05 va quvvat 0.8 bilan ko'rish uchun har guruhda 1191 mijoz kerak, ya'ni oyiga 200 + 200 bilan 6 oy. Samara 0.15 bo'lsa, 5139 mijoz va 26 oy kerak. Amalda bu "kichik samarani bu dizayn bilan aniqlab bo'lmaydi" degani va buni oldindan aytish kerak. Pilot bepul emas: top-200 o'rniga tasodifiy yarim top-400 ga qo'ng'iroq qilish oyiga ~`11.5` mln so'm kam tejam beradi. Bu raqam ijroiya xulosasidagi "Qaror uchun so'raladi" bandida turadi.
4-bo'lim — formula simulyatsiya bilan tekshirildi. 6 oylik dizayn (1200 + 1200) 2000 ta simulyatsiya qilingan pilotda 0.805 quvvat berdi, 26 oylik dizayn ham 0.805. Ikkalasi ham 0.8 ga mos. Taqqoslash uchun 29.1 dagi namuna rejadagi 3 oylik pilotning quvvati atigi 0.517. Haqiqiy samara 0.30 bo'lsa ham, pilot uni ikki holatdan birida "ko'rmaydi". Bu holatda jamoa noto'g'ri xulosaga kelishi mumkin edi: "qo'ng'iroqlar ishlamaydi".
5-bo'lim — xulosalar raqamdan chiqdi: "eng yaxshi segment" 3.5 barobar bo'rttirilgan va yangi ma'lumotda o'rtachaga qaytgan. Pilot ~6 oy bo'lishi kerak, 3 oy yetarli emas. Loyiha rejasi 29.1-bob shunga ko'ra yangilanadi: bu hayot siklining "orqaga qaytish" strelkasi (29.1, 2.1) amalda.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "Taqdimot tahlil tartibida: ma'lumot → model → natija" | Piramida: xulosa va tavsiya birinchi |
| "Aniq raqam — ishonchli raqam" | 42.3261 soxta aniqlik; "~40 (30-50)" halolroq va kuchliroq |
| "Foiz hammaga tushunarli" | "100 mijozdan ~14 tasi" tezroq tushuniladi |
| "Noaniqlikni aytsak, ishonch yo'qoladi" | Pastki chegara ham ijobiy — eng kuchli dalil |
| "Raqamni hisobotga qo'lda qo'yish tezroq" | Kelib chiqish tekshiruvi; generator har oy bir buyruq |
| "Kesilgan o'q farqni ko'rinarli qiladi" | Yolg'on koeffitsienti 8.4 — manipulyatsiya |
| "Eng yaxshi segmentni ko'rsatish — topilma" | 12 tasodifiy segmentdan eng yaxshisi har safar 2 barobardan katta |
| "Bir nechta test qilsak, bittasi albatta haqiqiy" | 20 kesimda A/A da ham 0.644 |
| "Pilot 3 oy — odatiy" | Hajm hisobi: 6 oy; 3 oyda quvvat 0.517 |
| "Qo'ng'iroq qilinganlar kamroq kechikdi — samara isbotlandi" | Faqat tasodifiy nazorat guruhi bilan |
6. Keng tarqalgan xatolar va yechimlari
1. Tavsiya oxirida
bolimlar = ["ma'lumot", "belgilar", "modellar", "natijalar", "tavsiya"] # ⚠️
bolimlar = ["xulosa va tavsiya", "dalillar", "xatarlar", "so'rov", "ilova"] # ✅2. Soxta aniqlik
print(f"tejam {tejam:.4f} mln so'm") # ⚠️
print(f"taxminan {yaxlitla(tejam, se):.0f} (oraliq {lo:.0f}-{hi:.0f})") # ✅3. Qo'lda raqam
hisobot = "Model oyiga 45 mln so'm tejaydi" # ⚠️
hisobot = SHABLON.format(**natijalar()); assert not kelib_chiqish(hisobot, R) # ✅4. Kesilgan o'q
ax.bar(nomlar, qiymatlar); ax.set_ylim(30, 43) # ⚠️
ax.bar(nomlar, qiymatlar); ax.set_ylim(0, max(qiymatlar) * 1.2) # ✅5. Eng yaxshi segment
print(f"eng yaxshi hudud: {max(hududlar, key=ustunlik.get)}") # ⚠️
print(segment_jadvali.to_string()); # hammasi, SE bilan, oldindan belgilangan # ✅6. Hisobsiz pilot muddati
pilot_oylar = 3 # ⚠️
pilot_oylar = math.ceil(n_har_guruh(p0, p1) / oylik_hajm) # ✅ + quvvat simulyatsiyasi7. Korrelyatsiyadan samara
samara = 1 - y[qongiroq].mean() / y[~qongiroq].mean() # ⚠️ boshqa mijozlar
samara = 1 - y[davolash].mean() / y[nazorat].mean() # tasodifiy taqsimot # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 5.12, 5.13-darslar (o'tilgan): Yaxshi va yolg'on grafiklar — avtomatik tekshiruv asosi
- 8.9-dars (o'tilgan): EDA hisoboti — auditoriya uchun yozish
- 11.2, 11.4, 11.9-darslar (o'tilgan): Quvvat, ulushlar testi, ko'p taqqoslash
- 27.13-dars (o'tilgan): A/B test — pilot dizayni
- 28.11-dars (o'tilgan): Sababiy xulosa va uplift — pilotdan keyingi qadam
- 29.5-dars: Kaggle va musobaqalar — leaderboard "shake-up" ham cherry-picking ning bir turi
- 29.7-dars: Portfolio — shu loyiha ijroiya xulosasi bilan eng kuchli namuna
- 29.10-dars: Intervyu case study — "natijangizni rahbarga qanday tushuntirasiz?"
8. Eng yaxshi amaliyotlar
Auditoriyani aniqlang: kim qanday qaror qabul qiladi.
Piramida: birinchi ikki gapda xulosa va tavsiya.
Har raqam — ma'noli aniqlik va oraliq bilan; faraziy parametrlar — sezgirlik bilan.
Tabiiy chastotalar: "100 mijozdan N tasi".
Hisobot koddan: natija lug'ati → shablon → kelib chiqish tekshiruvi → xesh.
Grafiklar avtomatik tekshiriladi: 0 dan, ≤ 4 qator, xulosa-sarlavha, twinx yo'q.
Barcha segmentlar ko'rsatiladi; ko'p taqqoslash hisobga olinadi.
Pilot hajmi hisoblanadi va simulyatsiya bilan tekshiriladi; narxi aytiladi; topshirish paketi to'liq.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # tejam 42.3261, SE 5.2249 - rahbarga qanday aytiladi?
2. # precision 0.1350 - tabiiy chastotada?
3. # samara 0.15 bo'lsa tejam taxminan qancha?
4. # hisobotdagi 42.3 qo'lda 45 ga o'zgartirilsa, tekshiruv nima deydi?
5. # o'q 30 dan boshlangan ustun grafikda 1.36 barobar farq necha barobar ko'rinadi?
6. # 12 tasodifiy segmentdan eng yaxshisi o'rtachadan necha barobar yuqori?
7. # o'sha segment yangi oyda qanday natija beradi?
8. # A/A da 20 kesimdan kamida bittasida p < 0.05 ehtimoli?
9. # samara 0.30 ni aniqlash uchun pilot necha oy? 3 oyda quvvat?
10. # pilotning oylik narxi?Javoblar
- "Taxminan 40 mln so'm, oraliq 30-50"
- "Ro'yxatdagi 100 mijozdan ~14 tasi"
- ~19.2 mln so'm/oy (chiziqli bog'liqlik)
- Bog'lanmagan son:
['45'] - 11.5 barobar (yolg'on koeffitsienti 8.4)
- ~3.5 barobar (+19.47 va +5.63); 100% holatda 2 barobardan katta
- O'rtachaga qaytadi (+3.32, o'rtacha +2.53)
- ~0.64 (0.644; nazariya 0.642)
- ~6 oy (1191 har guruhda); 3 oyda quvvat 0.517
- ~11.5 mln so'm/oy
Vazifa 2: Xatolarni tuzating
1. print(f"Model AUC {auc:.4f}, precision@200 {prec:.4f}. Tavsiya: joriy etish.")
2. hisobot = hisobot.replace("42.3", "42") # yaxlit raqam chiroyliroq
3. ax.bar(["Model", "Qoida"], [42.3, 31.1]); ax.set_ylim(30, 45); ax.set_title("Natija")
4. hududlar = {h: ustunlik(h) for h in HUDUDLAR}
print(f"Model eng yaxshi {max(hududlar, key=hududlar.get)} da - faqat u yerda joriy etamiz")
5. pilot = {"muddat_oy": 3, "guruhlar": "qo'ng'iroq qilinganlar va qilinmaganlar"}Javoblar
1. print(f"Model qoidadan oyiga ~{yaxlitla(farq, se):.0f} mln so'm ko'p tejaydi "
f"(oraliq {lo:.0f}-{hi:.0f}). Tavsiya: nazorat guruhli pilot.")
2. R["tejam_m_matn"] = yaxlitla(R["tejam_m"], R["tejam_se"]) # generatorda, lug'at orqali
3. ax.bar(["Model", "Qoida"], [m, q]); ax.set_ylim(0, m * 1.2)
ax.set_title(f"Model oyiga ~{m - q:.0f} mln so'm ko'p tejaydi")
4. print(segment_jadvali) # hamma hudud, SE bilan; farq 2*SE ichida bo'lsa - "farq yo'q"
5. pilot = {"muddat_oy": math.ceil(n / 200), "dizayn": "top-400 da tasodifiy 200/200",
"quvvat": 0.8, "tahlil_rejasi": "muhrlangan"}Vazifa 3: Piramida va noaniqlik
Modellang (1-misol asosida):
- Qo'ng'iroq markazi rahbari uchun alohida piramida yozing: uning qarori — ish tartibi va operatorlar soni (29.3 dagi sig'im egri chizig'idan foydalaning)
yaxlitlafunksiyasini "1 yoki 2 ta ma'noli raqam" qoidasiga o'zgartiring va natijalarni solishtiring- Samara, zarar koeffitsienti 0.45-bob va qo'ng'iroq narxi uchun uch o'lchovli sezgirlik jadvali — qaysi parametr natijaga eng ko'p ta'sir qiladi?
Vazifa 4: Hisobot generatori
Modellang (2-misol asosida):
- Hisobotga 3-misoldagi "yaxshi" grafikni qo'shing (markdown rasm havolasi) va grafik tekshiruvidan o'tmasa, hisobot yaratilmasin
- "O'tgan oyga nisbatan" bo'limi: ikki hisobot lug'atini solishtirib, o'zgargan raqamlarni ko'rsating
- Kelib chiqish tekshiruvini kuchaytiring: har son qaysi kalitdan kelganini chop eting
Vazifa 5: Grafiklar
Modellang (3-misol asosida):
- Tekshiruvga yana ikki qoida qo'shing: o'q yozuvlari bor-yo'qligi va rang ko'r (color-blind) uchun xavfli qizil-yashil juftligi
- Pirog (pie) grafik uchun tekshiruv: bo'laklar soni > 5 bo'lsa ogohlantirish
- 29.3 dagi 7 oylik CV natijalarini (4 nomzod) bitta "yaxshi" grafikda ko'rsating — qanday qilib spagetti bo'lmaydi?
Vazifa 6: Qilmaslik va pilot
Modellang (4-misol asosida):
- Cherry-picking simulyatsiyasini 3, 6, 12, 24 segment bilan takrorlang — "eng yaxshi" ning bo'rttirilishi segmentlar soniga qanday bog'liq?
- Benjamini-Hochberg 11.9-bob ni p-hacking simulyatsiyasiga qo'shing — Bonferroni bilan solishtiring
- Pilotni ketma-ket tahlil (har oy qarash) bilan simulyatsiya qiling — har oy "p < 0.05 bo'lsa to'xtatamiz" qoidasi yolg'on topilma ulushini qanchaga oshiradi?
- Pilot davrida sig'imni 300 ga oshirish (top-600, 300/300) — muddat va narx qanday o'zgaradi?
Vazifa 7: O'ylash
Boshqaruv a'zosi taqdimotdan keyin: "Bu noaniqliklar, oraliqlar, faraziy samara... Menga bitta raqam kerak. Boshqa jamoa bizga '60 mln so'm tejaymiz' deb aniq raqam bilan keldi. Sizning '3 dan 19 gacha'ngiz kuchsiz ko'rinadi. Pilotga 6 oy va yana 11.5 mln so'm/oy? Nega darhol joriy qilmaymiz?"
Javob
Qisqa javob: Bitta raqam beriladi, lekin oraliq va uni qisqartirish rejasi bilan birga. Pilot darhol joriy etishga alternativ emas, balki uning ichiga qurilgan o'lchov.
1. Bitta raqam. "Oyiga taxminan 11 mln so'm qo'shimcha" — bu bitta raqam. Oraliq (3-19) raqamni kuchsizlantirmaydi. U "hatto pessimistik holatda ham foyda bor" deydi. "60 mln" ga kelsak, bu qaysi farazda olinganini so'rash kerak. Bizning hisobimizda ham samara 0.45 bo'lsa tejam ~66 mln chiqadi. Ya'ni raqamlar orasidagi farq modelda emas, tekshirilmagan farazda bo'lishi mumkin.
2. Nega darhol "to'liq" emas.
# 1) samara 0.30-bob - FARAZ; 0.15 bo'lsa tejam ~19, 0.45 bo'lsa ~66 mln
# -> byudjet va operatorlar soni shu noma'lumga bog'liq
# 2) nazorat guruhisiz joriy etsak, samarani HECH QACHON o'lchay olmaymiz
# (qo'ng'iroq qilinganlar boshqa mijozlar - korrelyatsiya, 28.11)
# 3) 3 oylik pilot quvvati 0.517 - "ishlamaydi" degan yolg'on xulosa xatari3. Kelishuv taklifi. Pilot — bu joriy etish: model birinchi oydan ishlaydi, qo'ng'iroqlarning hammasi model ro'yxatidan. Faqat top-400 ichida tasodifiy yarmiga qo'ng'iroq qilinadi. Narxi (~11.5 mln/oy) — samarani o'lchash va keyingi yillar uchun to'g'ri byudjet qilish narxi. Yana bir foydasi bor: uplift modeli uchun ma'lumot yig'iladi 28.11-bob, u "kimga qo'ng'iroq ta'sir qiladi?" savoliga javob beradi.
Boshqaruv a'zosiga javob: "Bitta raqam: oyiga taxminan 11 mln so'm qo'shimcha tejam, hatto pessimistik holatda ham ijobiy. Boshqa jamoaning 60 mln i bilan farq, ehtimol, modelda emas, qo'ng'iroq qanchalik ta'sir qilishi haqidagi farazda. Buni hozir hech kim bilmaydi. Biz modelni ertadan ishga tushiramiz, lekin kichik nazorat guruhi bilan: 6 oyda bu faraz raqam bilan tasdiqlanadi yoki rad etiladi, va keyingi yil byudjeti taxminga emas, o'lchovga tayanadi."
Nimani mustahkamlaydi: 2.2, 2.3, 2.6, 2.7-bo'limlar.
Xulosa
Bu darsda loyihaning uchinchi qismini qildik: to'g'ri natijani qaror qabul qiluvchiga tushunarli, halol va qarorga tayyor qilib yetkazdik. Shu bilan 29.1-29.4 dagi to'liq loyiha yakunlandi: savoldan ma'lumotga, modelga, siyosatga va taqdimotga.
Eng muhim uch fikr:
Piramida va tushunarli noaniqlik. 1-misolda xom natija (
42.3261, SE5.2249) "taxminan 40, oraliq 30-50" ga aylandi. Qoidadan farq "+11, oraliq 3-19" bo'ldi, pastki chegara ham musbat. "Ro'yxatdagi 100 mijozdan ~14 tasi" (oddiy mijozlarda ~2), "oyiga ~8 mijoz kechikishdan saqlanadi" kabi jumlalar qo'ng'iroq markazi tilida. Sezgirlik eng katta noaniqlikni ko'rsatdi: bu statistik shovqin emas, faraziy samara (tejam19.2dan65.5gacha).Raqamlar koddan, grafiklar tekshiruvdan. 2-misolda hisobotdagi
17sonning hammasi natija lug'atidan olingan, qo'lda o'zgartirilgan45esa darhol ushlandi. Hisobot deterministik (bir xil xesh). 3-misolda kesilgan o'q1.36barobarlik farqni11.5barobar ko'rsatdi (yolg'on koeffitsienti8.4). Avtomatik tekshiruv yomon grafikda 5 ta buzilish, yaxshisida 0 ta buzilish topdi.Qilmaslik kerak bo'lgan narsalar o'lchandi, pilot hisoblandi. 4-misolda 12 ta tasodifiy segmentdan "eng yaxshisi" o'rtachadan
3.5barobar yuqori ko'rindi va yangi ma'lumotda o'rtachaga qaytdi. A/A da 20 kesimdan kamida bittasi0.644holatda "topilma" berdi. Pilot uchun samara 0.30 ni aniqlashga6 oy kerak (har guruhda11.5 mln so'm/oy) va topshirish paketi ijroiya xulosasiga yozildi.1191mijoz, quvvat simulyatsiyada0.805). 3 oylik pilotning quvvati esa0.517. Pilot narxi (
Keyingi darsda Kaggle va musobaqalar: to'liq loyihadan farqli, lekin ko'nikmalarni tez charxlaydigan format. Musobaqa qanday tuzilgani, public va private leaderboard, "shake-up" — bu darsdagi cherry-picking ning musobaqadagi ko'rinishi, validatsiya strategiyasi va musobaqa tajribasini portfolioga to'g'ri aylantirish.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!