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

28.11-dars: Sababiy xulosa (causal inference)

28-QISM — MAXSUS MAVZULAR · 11-dars


1. Kirish va motivatsiya

Oldingi darsda katta ma'lumot bilan ishlashni o'rgandik: million qatorlarni bo'laklab o'qish, oqimda hisoblash, taxminiy tuzilmalar. Lekin ma'lumot qancha katta bo'lmasin, u o'z-o'zidan bitta savolga javob bermaydi: "agar biz X ni qilsak, Y qanday o'zgaradi?" Bu — sabab haqidagi savol, va unga javob berish uchun hajm emas, dizayn kerak.

Kursning boshida (4.10, 8.4) "korrelyatsiya — sabab emas" degan qoidani o'rgangan edik: yashirin o'zgaruvchi, teskari bog'liqlik, Simpson paradoksi. 11-qismda va 27.13 da esa sababni aniqlashning eng ishonchli yo'lini — tasodifiy tajribani (A/B test) ko'rdik. Lekin amalda ko'p savollarga A/B test qilib bo'lmaydi yoki kech bo'ladi: kupon allaqachon bir yil davomida tarqatilgan, aksiya o'tib ketgan, siyosat butun shaharga birdan joriy qilingan. Qo'limizda faqat kuzatuv ma'lumoti bor. Bu darsda shunday ma'lumotdan sababiy ta'sirni qanday baholash, qachon bunga ishonish mumkin va — eng muhimi — qachon mumkin emasligini o'rganamiz.

Real vaziyat. Onlayn do'kon bir yil davomida mijozlarga 20% lik chegirma kuponi yubordi. Tahlil shuni ko'rsatdi: kupon olganlar keyingi oyda o'rtacha 60 ming so'm ko'proq xarid qilgan. Marketing "kupon xaridni 60 ming so'mga oshiradi" deb hisobot yozdi va byudjetni ikki barobar oshirishni so'radi. Lekin kuponlarni tizim sodiq mijozlarga ko'proq yuborgan edi — ular kuponsiz ham ko'p xarid qilardi. Keyinroq o'tkazilgan A/B test haqiqiy ta'sir 3 barobar kichik ekanini ko'rsatdi, sodiq mijozlarda esa deyarli nol. Bu darsning 2-misolida aynan shu vaziyatni sintetik ma'lumotda qayta quramiz — haqiqiy ta'sir ma'lum bo'lgani uchun har bir usulning xatosini o'lchay olamiz.

Bu darsda sababiy savolni to'g'ri qo'yishni, kuzatuv ma'lumotidan ta'sirni baholash usullarini va ularning farazlarini halol tekshirishni o'rganamiz.

Bu darsda:

  • Korrelyatsiya va sabab — nima uchun farq qiladi
  • Potentsial natijalar (Rubin): Y(1), Y(0), ATE, ATT, CATE va farazlar
  • DAG lar: chalkashtiruvchi, mediator, kollayder; orqa eshik mezoni; Simpson paradoksi
  • RCT — oltin standart
  • Kuzatuv ma'lumotida: regressiya bilan tuzatish, propensity score, moslashtirish, IPW
  • Overlap (positivity), balans tekshiruvi, og'irliklarni kesish
  • Ikki karra mustahkam baholash (AIPW) va bootstrap CI
  • O'lchanmagan chalkashtiruvchi — hamma usulning chegarasi; sezgirlik tahlili
  • Uplift modellashtirish: T-learner, S-learner, Qini egri chizig'i
  • Farqlar farqi (DiD) va parallel trendlar farazi
  • Tuzoqlar

ℹ Misollar real numpy/statsmodels/sklearn bilan (Python 3.14). DoWhy, EconML, CausalML bu muhitda yo'q — hamma usul noldan yoziladi. Ma'lumot sintetik va haqiqiy ta'sir ma'lum — shuning uchun har usulning xatosini o'lchaymiz.


2. Nazariya — chuqur tushuntirish

2.1. Korrelyatsiya va sabab

text
KUZATILGAN:  kupon olganlar 60 ming so'm ko'p xarid qildi
SAVOL:       kupon BERILSA, xarid qanchaga oshadi?

Farq manbalari:
  1. SABAB         kupon -> xarid                       (biz izlayotgan)
  2. CHALKASHTIRISH sodiqlik -> kupon, sodiqlik -> xarid (yashirin umumiy sabab)
  3. TESKARI        xarid -> kupon (ko'p xarid qilganga kupon yuboriladi)
  4. TANLASH        faqat "sharh qoldirganlar" tahlil qilindi (kollayder)
  5. TASODIF        kichik namuna, ko'p taqqoslash 11.9-bob

Korrelyatsiya = 1 + 2 + 3 + 4 + 5 aralashmasi.
Sababiy xulosa = 2-5 ni ajratib, faqat 1 ni baholash.

Bashorat va sabab — turli savollar. Bashorat modeli uchun "kupon olgan" belgisi foydali bo'lishi mumkin (u sodiqlikni ko'rsatadi), lekin bu kupon berish xaridni oshirishini anglatmaydi. 12-qismdan beri qurgan modellarimiz P(Y | X) ni o'rganadi; sababiy savol esa P(Y | do(X)) — "X ni biz o'zgartirganda".

2.2. Potentsial natijalar (Rubin modeli)

text
Har mijoz i uchun IKKI potentsial natija:
  Y_i(1) - kupon berilsa, keyingi oydagi xarid
  Y_i(0) - kupon berilmasa
Individual ta'sir:  tau_i = Y_i(1) - Y_i(0)

ASOSIY MUAMMO: har mijozda faqat BITTASI kuzatiladi
  Y_i = T_i * Y_i(1) + (1 - T_i) * Y_i(0)
  ikkinchisi - "kontrfaktual" (bo'lmagan olam)

O'RTACHA TA'SIRLAR (estimand - nimani baholaymiz):
  ATE  = E[Y(1) - Y(0)]            butun populyatsiya
  ATT  = E[Y(1) - Y(0) | T = 1]    kupon OLGANLAR orasida
  ATC  = E[Y(1) - Y(0) | T = 0]    olmaganlar orasida
  CATE = E[Y(1) - Y(0) | X = x]    belgilar bo'yicha (uplift)

NAIV FARQ nima beradi:
  E[Y | T=1] - E[Y | T=0]
    = ATT  +  ( E[Y(0) | T=1] - E[Y(0) | T=0] )
               \_____ tanlash siljishi (selection bias) _____/
  sodiqlar kupon olsa: E[Y(0)|T=1] > E[Y(0)|T=0] -> naiv farq OSHIRIB ko'rsatadi

Ta'sir turli mijozlarda turlicha bo'lsa, ATE va ATT farq qiladi. Kupon sodiq mijozlarga ko'proq berilgan va ularga kam ta'sir qilsa — ATT < ATE. "Kupon qancha foyda berdi?" (o'tgan dastur baholash) — ATT; "hammaga bersak-chi?" — ATE; "kimga berish kerak?" — CATE. Savolni tanlamasdan usul tanlab bo'lmaydi.

Kuzatuv ma'lumotidan sababiy ta'sirni baholash uchun farazlar:

text
1. IGNORABILITY (chalkashtiruvchi qolmagan):  (Y(1), Y(0)) mustaqil T | X
   X ni hisobga olgach, kupon olish "tasodifiy" - hamma umumiy sabablar X da
   TEKSHIRIB BO'LMAYDI - faqat domen bilimi bilan asoslanadi
2. POSITIVITY (overlap):  0 < P(T=1 | X) < 1  har X uchun
   har turdagi mijozda kupon olgan ham, olmagan ham bor
   TEKSHIRSA BO'LADI - propensity taqsimoti bilan
3. SUTVA / izchillik: bir mijozning kuponi boshqasiga ta'sir qilmaydi,
   "kupon" bitta aniq muolaja (20% lik, bir xil muddat)

2.3. DAG lar: nimani nazorat qilish kerak?

DAG (directed acyclic graph) — o'zgaruvchilar orasidagi sababiy strelkalar. Uchta asosiy tuzilma:

text
CHALKASHTIRUVCHI (fork)        MEDIATOR (chain)            KOLLAYDER
      sodiqlik                 kupon -> tashrif -> xarid    kupon      xarid
      /      \                  \_________________/^           \      /
     v        v                    (bevosita)                   v    v
  kupon ---> xarid                                             sharh

NAZORAT QILISH KERAK         UMUMIY ta'sir uchun NAZORAT    NAZORAT QILMASLIK
(orqa eshikni yopadi)        QILMASLIK kerak; nazorat       kerak! Kollayder bo'yicha
                             qilsangiz - faqat BEVOSITA     filtr/nazorat mustaqil
                             ta'sir qoladi                  kupon va xarid orasida
                                                            SOXTA bog'lanish yaratadi
text
ORQA ESHIK MEZONI (backdoor criterion):
  T dan Y ga ta'sirni baholash uchun Z to'plami yetarli, agar:
    1. Z hech bir elementi T ning avlodi (natijasi) emas
    2. Z T <- ... -> Y ko'rinishidagi barcha "orqa eshik" yo'llarni to'sadi
  shunda  ATE = sum_z ( E[Y | T=1, Z=z] - E[Y | T=0, Z=z] ) * P(Z=z)

"YOMON NAZORAT" (bad controls):
  mediator     - ta'sirning bir qismini yeb qo'yadi
  kollayder    - soxta bog'lanish ochadi
  T dan KEYIN o'lchangan har narsa (T ning natijasi bo'lishi mumkin)
QOIDA: "hamma belgini modelga qo'shaman" - sababiy tahlilda XATO

Nazorat qilish har doim ham yaxshi emas. Qaysi o'zgaruvchini nazorat qilish kerakligini ma'lumot emas, sababiy tuzilma hal qiladi — DAG ni ma'lumotga qaramasdan, domen bilimi bilan chizing.

2.4. Simpson paradoksi

8.4 da ko'rgan edik: guruhlarga ajratganda bog'liqlik yo'nalishi o'zgarishi mumkin. Sababiy nuqtai nazardan bu — chalkashtiruvchi (segment) ta'siri:

text
                  kuponsiz   kupon    farq
yangi mijozlar     5.0%       8.0%    +3.0   <- har segmentda kupon YORDAM beradi
doimiy mijozlar   30.0%      33.0%    +3.0
UMUMIY            ~20%       ~13%     MANFIY <- kuponlar asosan yangi mijozlarga
                                                (past bazaviy konversiya) berilgan
Qaysi javob to'g'ri? Segment kuponga SABAB bo'lgani uchun (segment -> kupon)
-> segment ichidagi farq va segment ulushlari bilan standartlash.
Agar segment kuponning NATIJASI bo'lsa (mediator) - umumiy farq to'g'ri bo'lardi.

2.5. RCT — oltin standart

Tasodifiy taqsimlashda (27.13 dagi A/B test, 11-qismdagi testlar) T hech narsaga bog'liq emas — na kuzatilgan, na kuzatilmagan belgilarga. Shuning uchun E[Y(0) | T=1] = E[Y(0) | T=0], tanlash siljishi nolga teng va oddiy farq ATE ni beradi. Kuzatuv ma'lumotidagi barcha usullar — RCT ni taqlid qilish urinishi: "X bir xil bo'lgan mijozlar orasida kupon tasodifiy berilgan deb faraz qilamiz". Imkon bo'lsa — tajriba qiling; kuzatuv tahlili tajribaning o'rnini bosmaydi, faqat tajriba mumkin bo'lmaganda eng yaxshi mavjud javobni beradi.

2.6. Kuzatuv ma'lumotida: to'rt yondashuv

text
1. REGRESSIYA BILAN TUZATISH
   Y ~ T + X  -> T koeffitsienti
   ta'sir X ga bog'liq bo'lsa - bu ATE EMAS, balki variansga qarab
   vaznlangan o'rtacha; model shakli noto'g'ri bo'lsa - siljish
   G-HISOBLASH (standartlash): mu_1(x) = E[Y | T=1, X=x] va mu_0(x) alohida
   ATE = mean_i ( mu_1(x_i) - mu_0(x_i) )

2. PROPENSITY SCORE:  e(x) = P(T = 1 | X = x)   (logistik regressiya, 13.10)
   Rosenbaum-Rubin: X bo'yicha ignorability bo'lsa, e(X) bo'yicha ham bor
   -> ko'p o'lchovli X o'rniga BITTA son bo'yicha tenglashtirish

3. MOSLASHTIRISH (matching):  har kupon olgan mijozga e(x) si eng yaqin
   kupon OLMAGAN mijoz; farqlar o'rtachasi -> ATT
   logit(e) bo'yicha, "caliper" (masalan 0.2 * std) - uzoq juftlar tashlanadi
   qaytarib (with replacement) - bitta nazorat bir necha marta ishlatiladi

4. IPW (inverse probability weighting):
   w = T / e  +  (1 - T) / (1 - e)
   g'oya: kupon olish ehtimoli past bo'lgan kupon oluvchi - "kamyob",
          u o'ziga o'xshash ko'p odam uchun "gapiradi"
   Hajek (normallangan):  sum(w*T*Y)/sum(w*T) - sum(w*(1-T)*Y)/sum(w*(1-T))
   ATT uchun: kupon olganlar w = 1, olmaganlar w = e / (1 - e)

2.7. Overlap, balans va og'irliklarni kesish

text
OVERLAP TEKSHIRUVI:
  e(x) taqsimotini guruhlar bo'yicha ko'ring (min, kvantillar, max)
  e ~ 0 yoki e ~ 1 bo'lgan hudud - u yerda taqqoslash uchun "egizak" yo'q
  maks og'irlik, samarali namuna hajmi: ESS = (sum w)^2 / sum w^2

BALANS (SMD - standartlashgan o'rtachalar farqi):
  SMD = (orta_1 - orta_0) / sqrt((var_1 + var_0) / 2)
  vaznlashdan (yoki moslashtirishdan) KEYIN |SMD| < 0.1 - odatiy mezon
  balans yo'q -> propensity modeli yetarli emas (belgilar, nochiziqlik)

KESISH (trimming/clipping):
  e ni [0.05, 0.95] ga kesish yoki o'sha hududdagi qatorlarni tashlash
  dispersiya keskin kamayadi, lekin BAHOLANADIGAN kattalik o'zgaradi:
  endi "overlap hududidagi mijozlar uchun ta'sir"  - buni hisobotda ayting

2.8. Ikki karra mustahkam baholash (AIPW)

text
AIPW (augmented IPW):
  psi_i = mu_1(x_i) - mu_0(x_i)
          + T_i * (Y_i - mu_1(x_i)) / e(x_i)
          - (1 - T_i) * (Y_i - mu_0(x_i)) / (1 - e(x_i))
  ATE = mean(psi_i)

NEGA "IKKI KARRA":
  natija modeli (mu) TO'G'RI bo'lsa - tuzatish hadlari o'rtachada 0 -> to'g'ri
  propensity (e) TO'G'RI bo'lsa     - tuzatish mu xatosini aynan yo'qotadi
  IKKALASI noto'g'ri bo'lsa         - kafolat yo'q
BONUS: ikkalasi yaxshi bo'lsa - dispersiya IPW dan ancha kichik
AMALDA: mu va e ni ML modellari (HistGB) bilan, cross-fitting bilan
        (bir qismda o'qitib, boshqasida baholash) - "double ML" g'oyasi

Noaniqlik. Sababiy baho — bitta son emas. Bootstrap 11.8-bob bilan ishonch oralig'i quriladi, lekin butun jarayon qayta bajarilishi kerak: propensity modelini ham har bootstrap namunasida qayta o'qitish.

2.9. O'lchanmagan chalkashtiruvchi — halol chegara

text
Barcha yuqoridagi usullar BITTA farazga tayanadi: hamma chalkashtiruvchilar X da.
Agar U (masalan, mijoz daromadi) kupon olishga ham, xaridga ham ta'sir qilsa
va ma'lumotda YO'Q bo'lsa:
  regressiya, moslashtirish, IPW, AIPW - HAMMASI siljigan
  va hech biri buni o'zi "sezmaydi" (balans X bo'yicha mukammal bo'lishi mumkin)

NIMA QILISH MUMKIN:
  sezgirlik tahlili - "U qanchalik kuchli bo'lsa, xulosa o'zgaradi?"
    (E-qiymat: kuzatilgan ta'sirni yo'qotish uchun U ning ham T, ham Y bilan
     bog'lanishi qanchalik kuchli bo'lishi kerak)
  boshqa dizayn: instrumental o'zgaruvchi (IV), regressiya uzilishi (RDD),
    farqlar farqi (DiD, 2.11), tabiiy tajriba
  eng yaxshisi - RCT

2.10. Uplift modellashtirish

Marketing uchun asosiy savol ko'pincha ATE emas, balki kimga berish: CATE(x) = E[Y(1) - Y(0) | X = x].

text
TO'RT SEGMENT (xarid ehtimoli bo'yicha):
                        kuponsiz OLMAYDI      kuponsiz OLADI
  kupon bilan OLADI     ISHONTIRILADIGAN      "BARIBIR OLADI"
                        (persuadables) +      (sure things) 0 - kupon isrof
  kupon bilan OLMAYDI   "YO'QOTILGAN"         "BEZOVTA QILMA"
                        (lost causes) 0       (sleeping dogs) - zarar!

JAVOB MODELI (xato!): P(xarid | X) yuqorilarga kupon -> asosan "baribir oladi"

META-LEARNER LAR:
  S-learner: bitta model f(X, T);   uplift = f(x, 1) - f(x, 0)
             kamchilik: model T ni "kuchsiz" belgi sifatida e'tiborsiz
             qoldirishi mumkin -> uplift nolga siqiladi
  T-learner: ikki model f1 (T=1 da), f0 (T=0 da); uplift = f1(x) - f0(x)
             kamchilik: ikki modelning xatolari qo'shiladi
  X-learner, DR-learner - ikkalasining yaxshi tomonlarini birlashtiradi

BAHOLASH - faqat RCT (yoki ishonchli tuzatilgan) ma'lumotda:
  QINI EGRI CHIZIG'I: mijozlarni bashorat qilingan uplift bo'yicha saralab,
  birinchi k ta ichida:
    Qini(k) = Y_1(k) - Y_0(k) * N_1(k) / N_0(k)
    (kupon olganlar xaridlari - kuponsizlar xaridlari, hajmga moslab)
  = "shu k ta mijozga kupon berilsa, qo'shimcha xaridlar soni"
  tasodifiy tartib - to'g'ri chiziq; maydon farqi - Qini koeffitsienti

Individual uplift ni to'g'ridan-to'g'ri tekshirib bo'lmaydi (har mijozda bitta natija). Shuning uchun baholash guruh darajasida: yuqori uplift deb baholangan guruhda kupon va kuponsizlar farqi haqiqatan kattami?

2.11. Farqlar farqi (difference-in-differences)

Aksiya faqat Samarqand do'konlarida 27-haftadan boshlandi. Toshkent do'konlari — nazorat.

text
                oldin (1-26 hafta)   keyin (27-52 hafta)   farq
Samarqand           A_oldin              A_keyin            A_keyin - A_oldin
Toshkent            B_oldin              B_keyin            B_keyin - B_oldin
DiD = (A_keyin - A_oldin) - (B_keyin - B_oldin)

NAIV 1: A_keyin - A_oldin      - trend va mavsumni ta'sir deb oladi
NAIV 2: A_keyin - B_keyin      - shaharlar orasidagi doimiy farqni ta'sir deb oladi
DiD: ikkalasini ham yo'qotadi, AGAR:

PARALLEL TRENDLAR FARAZI: aksiya bo'lmaganda Samarqand va Toshkent
  bir xil DINAMIKADA o'zgarardi (darajalari har xil bo'lishi mumkin)
  TEKSHIRUV: aksiyadan oldingi davrda "soxta aksiya" (placebo) DiD ~ 0 bo'lishi
  kerak; oldingi haftalar bo'yicha farqlar grafigi (event study)
  BUZILSA: Samarqand tezroq o'sayotgan bo'lsa - DiD o'sishni ta'sirga qo'shadi
SE: do'kon bo'yicha (klaster) - bir do'konning haftalari mustaqil emas

2.12. Tuzoqlar

Asosiy tuzoqlar: naiv farqni ta'sir deb e'lon qilish; estimandni (ATE/ATT/CATE) aniqlamasdan usul tanlash; "hamma belgini nazorat qilish" — mediator va kollayderni ham; muolajadan keyin o'lchangan belgilarni modelga qo'shish; overlap ni tekshirmasdan IPW; ekstremal og'irliklar va samarali namuna hajmini ko'rmaslik; balansni tekshirmaslik; bootstrap da propensity modelini qayta o'qitmaslik; kesish (trimming) baholanadigan kattalikni o'zgartirishini aytmaslik; o'lchanmagan chalkashtiruvchini e'tiborsiz qoldirish — "biz hamma narsani nazorat qildik"; uplift ni javob modeli bilan almashtirish; uplift modelni kuzatuv ma'lumotida (tuzatishsiz) baholash; DiD da parallel trendlarni tekshirmaslik; DiD da SE ni do'kon bo'yicha klasterlamaslik.


3. Tez ma'lumotnoma

python
import numpy as np
from sklearn.linear_model import LinearRegression, LogisticRegression

# propensity score
e = LogisticRegression(C=1e6, max_iter=1000).fit(X, T).predict_proba(X)[:, 1]

# overlap va balans
w = T / e + (1 - T) / (1 - e)
ess = w[T == 1].sum() ** 2 / (w[T == 1] ** 2).sum()


def smd(x, T, w):
    m1 = np.average(x[T == 1], weights=w[T == 1])
    m0 = np.average(x[T == 0], weights=w[T == 0])
    return (m1 - m0) / np.sqrt((x[T == 1].var() + x[T == 0].var()) / 2)


# g-hisoblash (T-learner) va AIPW
mu1 = LinearRegression().fit(X[T == 1], Y[T == 1]).predict(X)
mu0 = LinearRegression().fit(X[T == 0], Y[T == 0]).predict(X)
ate_g = np.mean(mu1 - mu0)
ate_ipw = (np.sum(T * Y / e) / np.sum(T / e)
           - np.sum((1 - T) * Y / (1 - e)) / np.sum((1 - T) / (1 - e)))
psi = mu1 - mu0 + T * (Y - mu1) / e - (1 - T) * (Y - mu0) / (1 - e)
ate_aipw = psi.mean()

# DiD (do'kon darajasida)
farq = y[:, keyin].mean(1) - y[:, oldin].mean(1)
did = farq[muolaja].mean() - farq[~muolaja].mean()
se = np.sqrt(farq[muolaja].var(ddof=1) / muolaja.sum()
             + farq[~muolaja].var(ddof=1) / (~muolaja).sum())

Haqiqiy loyihada (ma'lumotnoma; bu muhitda o'rnatilmagan)

python
# DoWhy - DAG bilan savolni qo'yish, baholash va "rad etish" testlari
from dowhy import CausalModel

model = CausalModel(data=df, treatment="kupon", outcome="xarid",
                    common_causes=["sodiqlik", "oldingi", "faollik"])
estimand = model.identify_effect()
baho = model.estimate_effect(estimand,
                             method_name="backdoor.propensity_score_weighting")
rad = model.refute_estimate(estimand, baho, method_name="placebo_treatment_refuter")

# EconML - CATE va double ML
from econml.dml import LinearDML
from econml.metalearners import TLearner
from sklearn.ensemble import HistGradientBoostingRegressor

dml = LinearDML(model_y=HistGradientBoostingRegressor(),
                model_t=HistGradientBoostingRegressor(), discrete_treatment=True)
dml.fit(Y, T, X=X_het, W=X_boshqa)
cate = dml.effect(X_het)

Qaysi vaziyatda nima

Vaziyat Usul Asosiy faraz
Tajriba qilish mumkin RCT / A/B test tasodifiy taqsimlash
Kuzatuv, chalkashtiruvchilar o'lchangan g-hisoblash, IPW, moslashtirish, AIPW ignorability + overlap
Modellarga to'liq ishonch yo'q AIPW (+ cross-fitting) bittasi to'g'ri
Guruh va vaqt, oldin/keyin DiD parallel trendlar
Chegara bo'yicha qaror (ball >= 70) RDD chegara atrofida taqqoslanuvchanlik
Tasodifiy "turtki" bor instrumental o'zgaruvchi turtki faqat T orqali ta'sir qiladi
Kimga berish kerak? uplift (T/S/X-learner) + Qini RCT ma'lumotida baholash

Sababiy xulosa xulosasi

savol -> estimand (ATE/ATT/CATE) -> DAG -> nazorat to'plami (orqa eshik)
mediator va kollayderni NAZORAT QILMA
kuzatuv: overlap -> balans (SMD) -> bir necha usul (reg, IPW, AIPW) + bootstrap CI
AIPW: bittasi to'g'ri bo'lsa yetadi; o'lchanmagan U -> hammasi yiqiladi
uplift: javob modeli emas; Qini - RCT da
DiD: parallel trendlar -> placebo tekshiruv, do'kon bo'yicha SE

4. Batafsil misollar

Misollar real numpy/statsmodels/sklearn bilan (Python 3.14). Har misol mustaqil ishlaydi; ma'lumot sintetik va haqiqiy ta'sir ma'lum.

Misol 1 — DAG lar simulyatsiyada: chalkashtiruvchi, mediator, kollayder, Simpson

python
"""DAG lar simulyatsiyada: nimani nazorat qilish kerak va nimani - yo'q."""

import numpy as np
import statsmodels.api as sm


def koef(y, *ustunlar):
    """OLS: birinchi regressor koeffitsienti va SE si."""
    r = sm.OLS(y, sm.add_constant(np.column_stack(ustunlar))).fit()
    return float(r.params[1]), float(r.bse[1])


def sigmoid(z):
    return 1 / (1 + np.exp(-z))


def main() -> None:
    rng = np.random.default_rng(0)
    n = 20_000
    xulosa = []

    print("=== 1. Chalkashtiruvchi: sodiqlik -> kupon, sodiqlik -> xarid ===")
    sod = rng.normal(0, 1, n)
    kupon = (rng.random(n) < sigmoid(1.5 * sod - 0.5)).astype(float)
    xarid = 200 + 60 * sod + 30 * kupon + rng.normal(0, 40, n)
    naiv = xarid[kupon == 1].mean() - xarid[kupon == 0].mean()
    b1, _ = koef(xarid, kupon)
    b2, s2 = koef(xarid, kupon, sod)
    print("  haqiqiy ta'sir: 30.0 ming so'm")
    print(f"  sodiqlik o'rtachasi: kupon {sod[kupon == 1].mean():+.2f}, "
          f"kuponsiz {sod[kupon == 0].mean():+.2f}")
    print(f"  naiv farq               {naiv:6.1f}")
    print(f"  xarid ~ kupon + sodiq   {b2:6.1f} (SE {s2:.2f})")
    xulosa.append(("chalkashtiruvchi", naiv, b2, s2, 30.0))

    print("\n=== 2. Mediator: kupon -> tashriflar -> xarid (kupon tasodifiy) ===")
    kupon = rng.integers(0, 2, n).astype(float)
    tashrif = 2 + 1.5 * kupon + rng.normal(0, 1, n)
    xarid = 100 + 20 * tashrif + 10 * kupon + rng.normal(0, 40, n)
    b1, s1 = koef(xarid, kupon)
    b2, s2 = koef(xarid, kupon, tashrif)
    print("  haqiqiy: umumiy ta'sir 10 + 20 * 1.5 = 40.0, bevosita 10.0")
    print(f"  xarid ~ kupon            {b1:6.1f} (SE {s1:.2f})  <- umumiy")
    print(f"  xarid ~ kupon + tashrif  {b2:6.1f} (SE {s2:.2f})  <- faqat bevosita")
    xulosa.append(("mediator (umumiy)", b2, b1, s1, 40.0))

    print("\n=== 3. Kollayder: kupon -> sharh <- xarid (kupon ta'sir QILMAYDI) ===")
    kupon = rng.integers(0, 2, n).astype(float)
    xarid = 200 + rng.normal(0, 50, n)
    sharh = rng.random(n) < sigmoid(-2 + 1.5 * kupon + 0.04 * (xarid - 200))
    b1, s1 = koef(xarid, kupon)
    b2, s2 = koef(xarid, kupon, sharh.astype(float))
    ichida = (xarid[sharh & (kupon == 1)].mean()
              - xarid[sharh & (kupon == 0)].mean())
    print("  haqiqiy ta'sir: 0.0")
    print(f"  hamma mijozlar: xarid ~ kupon          {b1:6.1f} (SE {s1:.2f})")
    print(f"  xarid ~ kupon + sharh                  {b2:6.1f} (SE {s2:.2f})")
    print(f"  faqat sharh qoldirganlar ({sharh.mean():.1%}): farq {ichida:6.1f}")
    xulosa.append(("kollayder", b2, b1, s1, 0.0))

    print("\n=== 4. Simpson paradoksi: segment -> kupon, segment -> konversiya ===")
    n2 = 40_000
    doimiy = rng.random(n2) < 0.4
    kupon = rng.random(n2) < np.where(doimiy, 0.2, 0.7)
    p = np.where(doimiy, 0.30, 0.05) + 0.03 * kupon
    oldi = rng.random(n2) < p
    print(f"  {'segment':<10} {'kuponsiz':>9} {'kupon':>7} {'farq':>7} "
          f"{'kupon ulushi':>13}")
    farqlar, ulushlar = [], []
    for nom, s in [("yangi", ~doimiy), ("doimiy", doimiy)]:
        k0, k1 = oldi[s & ~kupon].mean(), oldi[s & kupon].mean()
        farqlar.append(k1 - k0)
        ulushlar.append(s.mean())
        print(f"  {nom:<10} {k0:>9.3f} {k1:>7.3f} {k1 - k0:>+7.3f} "
              f"{kupon[s].mean():>13.2f}")
    u0, u1 = oldi[~kupon].mean(), oldi[kupon].mean()
    print(f"  {'UMUMIY':<10} {u0:>9.3f} {u1:>7.3f} {u1 - u0:>+7.3f} "
          f"{kupon.mean():>13.2f}")
    standart = float(np.dot(farqlar, ulushlar))
    print(f"  standartlangan (segment ulushlari bilan): {standart:+.3f} "
          f"(haqiqiy +0.030)")

    print("\n=== 5. Xulosa (natijadan hisoblangan) ===")
    for nom, yomon, yaxshi, se, haq in xulosa:
        print(f"  {nom:<18} noto'g'ri tanlov {yomon:6.1f}, to'g'ri {yaxshi:6.1f}"
              f" (haqiqiy {haq:.1f}); to'g'ri 2*SE ichida: "
              f"{abs(yaxshi - haq) <= 2 * se}")
    print("  ⭐ Nazorat to'plamini DAG hal qiladi, ma'lumot emas")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Chalkashtiruvchi: sodiqlik -> kupon, sodiqlik -> xarid ===
  haqiqiy ta'sir: 30.0 ming so'm
  sodiqlik o'rtachasi: kupon +0.63, kuponsiz -0.43
  naiv farq                 94.4
  xarid ~ kupon + sodiq     31.1 (SE 0.67)

=== 2. Mediator: kupon -> tashriflar -> xarid (kupon tasodifiy) ===
  haqiqiy: umumiy ta'sir 10 + 20 * 1.5 = 40.0, bevosita 10.0
  xarid ~ kupon              39.2 (SE 0.63)  <- umumiy
  xarid ~ kupon + tashrif     9.1 (SE 0.70)  <- faqat bevosita

=== 3. Kollayder: kupon -> sharh <- xarid (kupon ta'sir QILMAYDI) ===
  haqiqiy ta'sir: 0.0
  hamma mijozlar: xarid ~ kupon             0.7 (SE 0.71)
  xarid ~ kupon + sharh                   -11.9 (SE 0.59)
  faqat sharh qoldirganlar (32.6%): farq  -14.4

=== 4. Simpson paradoksi: segment -> kupon, segment -> konversiya ===
  segment     kuponsiz   kupon    farq  kupon ulushi
  yangi          0.052   0.081  +0.029          0.70
  doimiy         0.301   0.335  +0.034          0.20
  UMUMIY         0.211   0.121  -0.090          0.50
  standartlangan (segment ulushlari bilan): +0.031 (haqiqiy +0.030)

=== 5. Xulosa (natijadan hisoblangan) ===
  chalkashtiruvchi   noto'g'ri tanlov   94.4, to'g'ri   31.1 (haqiqiy 30.0); to'g'ri 2*SE ichida: True
  mediator (umumiy)  noto'g'ri tanlov    9.1, to'g'ri   39.2 (haqiqiy 40.0); to'g'ri 2*SE ichida: True
  kollayder          noto'g'ri tanlov  -11.9, to'g'ri    0.7 (haqiqiy 0.0); to'g'ri 2*SE ichida: True
  ⭐ Nazorat to'plamini DAG hal qiladi, ma'lumot emas

Natija tahlili.

1-bo'lim — chalkashtiruvchi. Kupon olganlarning o'rtacha sodiqligi +0.63, olmaganlarniki -0.43 — tizim kuponni sodiq mijozlarga ko'proq bergan. Naiv farq 94.4 ming so'm — haqiqiy ta'sirdan (30) uch barobar katta: farqning katta qismi kupondan emas, sodiqlikdan. Sodiqlikni nazorat qilgach (xarid ~ kupon + sodiqlik) 31.1 (SE 0.67) — orqa eshik yopildi.

2-bo'lim — mediator. Kupon tasodifiy berilgan (RCT), shuning uchun oddiy regressiya 39.2 — umumiy ta'sirni (40) to'g'ri beradi. "Ko'proq nazorat — yaxshiroq" degan fikr bilan tashriflar sonini qo'shsak, koeffitsient 9.1 ga tushadi — bu faqat bevosita ta'sir (tashrif orqali o'tmaydigan qism). Marketing uchun "kupon xaridni qanchaga oshiradi" savoliga bu javob uch barobar kam baholaydi. Mediatorni nazorat qilish faqat "ta'sir qaysi yo'l bilan o'tadi" degan boshqa savolga javob beradi.

3-bo'lim — kollayder. Kupon xaridga umuman ta'sir qilmaydi, butun ma'lumotda farq 0.7 (SE 0.71) — to'g'ri. Lekin sharh qoldirish kupondan ham, katta xariddan ham kelib chiqadi. Faqat sharh qoldirganlarni (32.6%) tahlil qilsak — kupon "xaridni 14.4 ming so'mga kamaytiradi". Mantiq: sharh qoldirgan kuponsiz mijoz buni katta xarid tufayli qilgan bo'lishi ehtimoli yuqori; kupon oluvchida esa sharh uchun boshqa sabab bor. Regressiyaga sharhni qo'shish ham xuddi shu soxta ta'sirni beradi (-11.9). Amalda bu juda ko'p uchraydi: "faqat ilovani o'rnatganlar", "faqat so'rovnomaga javob berganlar" — tanlov o'zi kollayder bo'lishi mumkin.

4-bo'lim — Simpson paradoksi. Har ikkala segmentda kupon konversiyani oshiradi (+0.029 va +0.034), lekin umumiy jadvalda kupon olganlar konversiyasi past (0.121 va 0.211, farq -0.090). Sabab: kuponlarning 70% i past konversiyali yangi mijozlarga berilgan. Segment kuponga sabab bo'lgani uchun to'g'ri javob — segment ichidagi farqlar, segment ulushlari bilan standartlangan: +0.031 (haqiqiy +0.030).

5-bo'lim jadvali uchta holatni bitta qatorda ko'rsatadi: bir xil ma'lumot, bir xil regressiya — faqat qaysi o'zgaruvchi nazorat qilingani o'zgardi, va javob butunlay o'zgardi. To'g'ri tanlov har safar haqiqiy qiymatning 2*SE oralig'ida.

Misol 2 — Kupon ta'siri: naiv farq, regressiya, moslashtirish, IPW va AIPW

python
"""Kupon ta'siri kuzatuv ma'lumotida: usullarni haqiqiy ATE/ATT bilan solishtirish."""

import numpy as np
from sklearn.linear_model import LinearRegression, LogisticRegression
from sklearn.neighbors import NearestNeighbors

USTUNLAR = ["sodiqlik", "log_oldingi", "faollik", "toshkent"]
MAQSAD = {"naiv farq": "ATE", "regressiya T+X": "ATE", "g-hisoblash": "ATE",
          "moslashtirish": "ATT", "IPW": "ATE", "IPW ATT": "ATT",
          "IPW kesilgan": "ATE", "AIPW": "ATE"}


def yarat(seed, n=4000):
    """Sodiq mijozlar kuponni ko'proq oladi va kuponsiz ham ko'p xarid qiladi."""
    rng = np.random.default_rng(seed)
    sod = rng.normal(0, 1, n)
    oldingi = rng.poisson(np.exp(1.0 + 0.5 * sod))
    faol = rng.normal(0, 1, n)
    toshkent = (rng.random(n) < 0.45).astype(float)
    X = np.column_stack([sod, np.log1p(oldingi), faol, toshkent])
    logit = -0.6 + 1.3 * sod + 0.4 * np.log1p(oldingi) + 0.5 * faol - 0.3 * toshkent
    T = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
    tau = 40 - 15 * sod + 8 * faol                   # sodiqlarga kam ta'sir
    y0 = 180 + 55 * sod + 20 * np.log1p(oldingi) + 15 * faol + 20 * toshkent
    Y = y0 + tau * T + rng.normal(0, 60, n)          # ming so'm
    return X, T, Y, tau


def propensity(X, T):
    return LogisticRegression(C=1e6, max_iter=1000).fit(X, T).predict_proba(X)[:, 1]


def usullar(X, T, Y):
    e = propensity(X, T)
    b = {"naiv farq": Y[T == 1].mean() - Y[T == 0].mean()}
    b["regressiya T+X"] = LinearRegression().fit(
        np.column_stack([T, X]), Y).coef_[0]
    mu1 = LinearRegression().fit(X[T == 1], Y[T == 1]).predict(X)
    mu0 = LinearRegression().fit(X[T == 0], Y[T == 0]).predict(X)
    b["g-hisoblash"] = np.mean(mu1 - mu0)
    lg = np.log(e / (1 - e))
    nn = NearestNeighbors(n_neighbors=1).fit(lg[T == 0, None])
    d, j = nn.kneighbors(lg[T == 1, None])
    ok = d[:, 0] <= 0.2 * lg.std()
    b["moslashtirish"] = np.mean(Y[T == 1][ok] - Y[T == 0][j[ok, 0]])

    def hajek(e_):
        w1, w0 = T / e_, (1 - T) / (1 - e_)
        return np.sum(w1 * Y) / np.sum(w1) - np.sum(w0 * Y) / np.sum(w0)

    b["IPW"] = hajek(e)
    w0 = (1 - T) * e / (1 - e)
    b["IPW ATT"] = Y[T == 1].mean() - np.sum(w0 * Y) / np.sum(w0)
    b["IPW kesilgan"] = hajek(np.clip(e, 0.05, 0.95))
    psi = mu1 - mu0 + T * (Y - mu1) / e - (1 - T) * (Y - mu0) / (1 - e)
    b["AIPW"] = psi.mean()
    return {k: float(v) for k, v in b.items()}, e, float(ok.mean())


def smd(x, T, w):
    m1 = np.average(x[T == 1], weights=w[T == 1])
    m0 = np.average(x[T == 0], weights=w[T == 0])
    return (m1 - m0) / np.sqrt((x[T == 1].var() + x[T == 0].var()) / 2)


def main() -> None:
    X, T, Y, tau = yarat(0)
    ate, att = tau.mean(), tau[T == 1].mean()
    haq = {"ATE": ate, "ATT": att}

    print("=== 1. Ma'lumot (urug' 0): kim kupon oldi? ===")
    print(f"  n={len(T)}, kupon olganlar ulushi {T.mean():.3f}")
    print(f"  haqiqiy ATE {ate:.2f}, ATT {att:.2f} ming so'm "
          f"(sodiqlarga ta'sir kam -> ATT < ATE)")

    print("\n=== 2. Overlap va balans ===")
    b, e, moslandi = usullar(X, T, Y)
    for g, nom in [(1, "kupon"), (0, "kuponsiz")]:
        q = np.quantile(e[T == g], [0, 0.05, 0.5, 0.95, 1])
        print(f"  {nom:<8} e: min {q[0]:.3f} 5% {q[1]:.3f} med {q[2]:.3f} "
              f"95% {q[3]:.3f} max {q[4]:.3f}")
    print(f"  e < 0.05 yoki e > 0.95: {np.mean((e < 0.05) | (e > 0.95)):.3f} ulush")
    w = T / e + (1 - T) / (1 - e)
    for g, nom in [(1, "kupon"), (0, "kuponsiz")]:
        wg = w[T == g]
        print(f"  {nom:<8} maks og'irlik {wg.max():6.1f}, ESS "
              f"{wg.sum() ** 2 / (wg ** 2).sum():6.0f} / {len(wg)}")
    print(f"  moslashtirish: caliper ichida {moslandi:.3f} kupon oluvchi")
    print(f"  {'belgi':<12} {'SMD xom':>8} {'SMD IPW':>8}")
    bir = np.ones(len(T))
    for j, u in enumerate(USTUNLAR):
        print(f"  {u:<12} {smd(X[:, j], T, bir):>+8.3f} "
              f"{smd(X[:, j], T, w):>+8.3f}")

    print("\n=== 3. Usullar (urug' 0) va bootstrap 95% CI (200 takror) ===")
    rng = np.random.default_rng(123)
    bs = {k: [] for k in b}
    for _ in range(200):
        i = rng.integers(0, len(T), len(T))
        bi, _, _ = usullar(X[i], T[i], Y[i])
        for k in bi:
            bs[k].append(bi[k])
    print(f"  {'usul':<15} {'maqsad':>6} {'baho':>7} {'95% CI':>17} "
          f"{'haqiqiy':>8} {'qamradi':>8}")
    for k, v in b.items():
        lo, hi = np.percentile(bs[k], [2.5, 97.5])
        h = haq[MAQSAD[k]]
        print(f"  {k:<15} {MAQSAD[k]:>6} {v:>7.2f} [{lo:>6.2f}, {hi:>6.2f}] "
              f"{h:>8.2f} {str(lo <= h <= hi):>8}")

    print("\n=== 4. 30 urug': xato = baho - haqiqiy (o'z maqsadiga nisbatan) ===")
    xato = {k: [] for k in MAQSAD}
    for s in range(1, 31):
        X, T, Y, tau = yarat(s)
        bi, _, _ = usullar(X, T, Y)
        h = {"ATE": tau.mean(), "ATT": tau[T == 1].mean()}
        for k in bi:
            xato[k].append(bi[k] - h[MAQSAD[k]])
    print(f"  {'usul':<15} {'o_rt xato':>9} {'SE':>6} {'RMSE':>7} {'siljigan':>9}")
    siljimagan = []
    for k in MAQSAD:
        x = np.array(xato[k])
        se = x.std(ddof=1) / np.sqrt(len(x))
        rmse = float(np.sqrt(np.mean(x ** 2)))
        silj = abs(x.mean()) > 2 * se
        if not silj:
            siljimagan.append((rmse, k))
        print(f"  {k:<15} {x.mean():>+9.2f} {se:>6.2f} {rmse:>7.2f} "
              f"{str(silj):>9}")
    siljimagan.sort()
    print(f"  siljimaganlar ichida eng kichik RMSE: {siljimagan[0][1]} "
          f"({siljimagan[0][0]:.2f})")
    print("  ⭐ Naiv farq - chalkashtirish; tuzatish - faqat o'lchangan X bo'yicha")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Ma'lumot (urug' 0): kim kupon oldi? ===
  n=4000, kupon olganlar ulushi 0.452
  haqiqiy ATE 40.22, ATT 33.17 ming so'm (sodiqlarga ta'sir kam -> ATT < ATE)

=== 2. Overlap va balans ===
  kupon    e: min 0.014 5% 0.187 med 0.647 95% 0.943 max 0.992
  kuponsiz e: min 0.003 5% 0.043 med 0.267 95% 0.759 max 0.970
  e < 0.05 yoki e > 0.95: 0.056 ulush
  kupon    maks og'irlik   69.1, ESS    753 / 1808
  kuponsiz maks og'irlik   32.8, ESS   1251 / 2192
  moslashtirish: caliper ichida 0.992 kupon oluvchi
  belgi         SMD xom  SMD IPW
  sodiqlik       +1.187   -0.024
  log_oldingi    +0.762   -0.009
  faollik        +0.311   +0.031
  toshkent       -0.071   +0.017

=== 3. Usullar (urug' 0) va bootstrap 95% CI (200 takror) ===
  usul            maqsad    baho            95% CI  haqiqiy  qamradi
  naiv farq          ATE  102.44 [ 98.01, 107.43]    40.22    False
  regressiya T+X     ATE   40.01 [ 35.84,  44.25]    40.22     True
  g-hisoblash        ATE   40.89 [ 36.78,  45.32]    40.22     True
  moslashtirish      ATT   32.95 [ 28.23,  42.67]    33.17     True
  IPW                ATE   38.05 [ 29.59,  47.06]    40.22     True
  IPW ATT            ATT   35.01 [ 28.03,  43.73]    33.17     True
  IPW kesilgan       ATE   41.52 [ 35.23,  48.12]    40.22     True
  AIPW               ATE   38.32 [ 32.30,  44.09]    40.22     True

=== 4. 30 urug': xato = baho - haqiqiy (o'z maqsadiga nisbatan) ===
  usul            o_rt xato     SE    RMSE  siljigan
  naiv farq          +62.57   0.39   62.61      True
  regressiya T+X      -0.57   0.37    2.07     False
  g-hisoblash         +0.41   0.38    2.07     False
  moslashtirish       +0.51   0.72    3.91     False
  IPW                 +0.49   0.55    3.00     False
  IPW ATT             +1.11   0.81    4.50     False
  IPW kesilgan        +3.69   0.51    4.59      True
  AIPW                +0.38   0.45    2.44     False
  siljimaganlar ichida eng kichik RMSE: g-hisoblash 2.07-bob
  ⭐ Naiv farq - chalkashtirish; tuzatish - faqat o'lchangan X bo'yicha

Natija tahlili.

1-bo'lim — mijozlarning 45.2% i kupon olgan. Haqiqiy ATE 40.22, ATT 33.17 ming so'm: kupon asosan sodiq mijozlarga borgan, ularga esa ta'sir kichikroq — shuning uchun "kupon olganlar uchun ta'sir" (ATT) "hamma uchun ta'sir" dan (ATE) past.

2-bo'lim — overlap va balans. Propensity taqsimotlari keng ustma-ust tushadi, lekin chetlar bor: kupon olganlarda eng kichik e = 0.014, kuponsizlarda eng katta 0.970; 5.6% qator [0.05, 0.95] dan tashqarida. IPW og'irliklarining maksimumi 69.1 — bitta mijoz 69 kishi uchun "gapiradi"; samarali namuna hajmi kupon olganlarda 1808 dan 753 ga tushdi. Balans: xom ma'lumotda sodiqlik bo'yicha SMD +1.187 (juda katta nomutanosiblik), IPW dan keyin -0.024; hamma belgi |SMD| < 0.1 — propensity modeli o'z vazifasini bajargan. Moslashtirishda kupon oluvchilarning 99.2% i caliper ichida juft topdi.

3-bo'lim — bitta ma'lumotda baholar va bootstrap CI (propensity modeli har bootstrap namunasida qayta o'qitildi). Naiv farq 102.44, CI [98.01, 107.43] — haqiqiy ATE (40.22) dan juda uzoq va CI uni qamramaydi: katta namuna "aniq noto'g'ri" javob beradi. Qolgan hamma usul o'z maqsadini qamradi. CI kengligiga e'tibor bering: g-hisoblash ~8.5, AIPW ~11.8, IPW ~17.5 — og'irliklar ekstremal bo'lsa, IPW eng shovqinli. Moslashtirish va IPW ATT ATT ni (33.17) baholaydi — ularni ATE bilan solishtirish xato bo'lardi.

4-bo'lim — 30 ta mustaqil ma'lumotda. Naiv farq o'rtacha +62.57 ga siljigan — tasodif emas, tizimli xato. Regressiya (-0.57), g-hisoblash (+0.41), moslashtirish, IPW va AIPW — sezilarli siljish yo'q. Kesilgan IPW esa +3.69 ga siljigan: e ni [0.05, 0.95] ga kesish og'irliklarni o'zgartiradi, va baholanadigan kattalik endi aniq ATE emas — dispersiya bilan siljish almashtirildi. RMSE bo'yicha g-hisoblash va oddiy regressiya eng yaxshi (2.07), AIPW 2.44, IPW 3.00. Bu ma'lumotda natija modeli (chiziqli) haqiqatga yaqin, shuning uchun unga tayanadigan usullar eng aniq. Ta'sir sodiqlikka bog'liq bo'lsa ham, T + X regressiyasining siljishi bu yerda sezilarli chiqmadi; lekin umuman olganda u ATE emas, balki vaznlangan o'rtachani baholaydi. Keyingi misolda natija modeli noto'g'ri bo'lganda bu tartib qanday o'zgarishini ko'ramiz.

Misol 3 — Mustahkamlik: noto'g'ri modellar, o'lchanmagan chalkashtiruvchi, kuchsiz overlap

python
"""Ikki karra mustahkamlik, o'lchanmagan chalkashtiruvchi va overlap - 30 urug'da."""

import numpy as np
from sklearn.linear_model import LinearRegression, LogisticRegression

HAQIQIY = 20.0
USULLAR = ["regressiya", "IPW", "IPW kesilgan", "AIPW"]


def yarat(rng, n, kuch=1.0, u_kuch=0.0):
    """x1 ning kvadrati ham tanlashga, ham natijaga ta'sir qiladi; u - yashirin."""
    x1, x2, u = rng.normal(0, 1, (3, n))
    logit = kuch * (-0.8 + 0.8 * x1 + 0.7 * (x1 ** 2 - 1) + 0.5 * x2) + u_kuch * u
    T = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
    Y = (100 + 20 * x1 + 25 * (x1 ** 2 - 1) + 10 * x2 + 25 * u_kuch * u
         + HAQIQIY * T + rng.normal(0, 30, n))
    return x1, x2, T, Y


def baholar(Xn, Xp, T, Y):
    """Xn - natija modeli belgilari, Xp - propensity modeli belgilari."""
    e = LogisticRegression(C=1e6, max_iter=1000).fit(Xp, T).predict_proba(Xp)[:, 1]
    e = np.clip(e, 1e-6, 1 - 1e-6)          # faqat 0 ga bo'lishdan saqlanish
    mu1 = LinearRegression().fit(Xn[T == 1], Y[T == 1]).predict(Xn)
    mu0 = LinearRegression().fit(Xn[T == 0], Y[T == 0]).predict(Xn)

    def hajek(e_):
        w1, w0 = T / e_, (1 - T) / (1 - e_)
        return np.sum(w1 * Y) / np.sum(w1) - np.sum(w0 * Y) / np.sum(w0)

    psi = mu1 - mu0 + T * (Y - mu1) / e - (1 - T) * (Y - mu0) / (1 - e)
    w = T / e + (1 - T) / (1 - e)
    return ({"regressiya": np.mean(mu1 - mu0), "IPW": hajek(e),
             "IPW kesilgan": hajek(np.clip(e, 0.05, 0.95)), "AIPW": psi.mean()},
            float(w.max()), float(w.sum() ** 2 / (w ** 2).sum() / len(w)))


def tajriba(kuch, u_kuch, natija_togri, ps_togri, urug=30, n=3000):
    xatolar = {k: [] for k in USULLAR}
    maks_w, ess = [], []
    for s in range(urug):
        rng = np.random.default_rng(s)
        x1, x2, T, Y = yarat(rng, n, kuch, u_kuch)
        togri = np.column_stack([x1, x1 ** 2, x2])
        notogri = np.column_stack([x1, x2])
        b, mw, es = baholar(togri if natija_togri else notogri,
                            togri if ps_togri else notogri, T, Y)
        for k in USULLAR:
            xatolar[k].append(b[k] - HAQIQIY)
        maks_w.append(mw)
        ess.append(es)
    return ({k: np.array(v) for k, v in xatolar.items()},
            float(np.median(maks_w)), float(np.mean(ess)))


def katak(x):
    se = x.std(ddof=1) / np.sqrt(len(x))
    belgi = "*" if abs(x.mean()) > 2 * se else " "
    return f"{x.mean():+6.1f}{belgi}({x.std(ddof=1):4.1f})"


def main() -> None:
    print("=== 1. Ikki karra mustahkamlik: 30 urug', haqiqiy ATE = 20 ===")
    print("  katak: o'rtacha xato, * - sezilarli siljish (> 2 SE), (std)")
    ssenariylar = [
        ("ikkala model to'g'ri", 1.0, 0.0, True, True),
        ("natija modeli xato", 1.0, 0.0, False, True),
        ("propensity xato", 1.0, 0.0, True, False),
        ("ikkalasi xato", 1.0, 0.0, False, False),
    ]
    print(f"  {'ssenariy':<22}" + "".join(f"{k:>15}" for k in USULLAR))
    for nom, *arg in ssenariylar:
        x, _, _ = tajriba(*arg)
        print(f"  {nom:<22}" + "".join(f"{katak(x[k]):>15}" for k in USULLAR))

    print("\n=== 2. Kuchsiz overlap: tanlash 3 barobar kuchli (modellar to'g'ri) ===")
    for nom, kuch in [("oddiy tanlash", 1.0), ("kuchli tanlash", 3.0)]:
        x, mw, ess = tajriba(kuch, 0.0, True, True)
        print(f"  {nom:<16} maks og'irlik (mediana) {mw:8.1f}, ESS ulushi "
              f"{ess:.3f}")
        print("    " + "  ".join(f"{k}: {katak(x[k])}" for k in USULLAR[1:]))

    print("\n=== 3. O'lchanmagan chalkashtiruvchi u (modellar x bo'yicha to'g'ri) ===")
    print(f"  {'u kuchi':>8}" + "".join(f"{k:>15}" for k in USULLAR))
    for u_kuch in [0.0, 0.25, 0.5, 1.0]:
        x, _, _ = tajriba(1.0, u_kuch, True, True)
        print(f"  {u_kuch:>8}" + "".join(f"{katak(x[k]):>15}" for k in USULLAR))
    x, _, _ = tajriba(1.0, 1.0, True, True)
    hammasi = all(abs(x[k].mean()) > 2 * x[k].std(ddof=1) / np.sqrt(30)
                  for k in USULLAR)
    print(f"  u kuchi 1.0 da hamma usul sezilarli siljigan: {hammasi}")
    print("  ⭐ AIPW: bittasi to'g'ri bo'lsa yetadi; yashirin u ga qarshi - ojiz")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Ikki karra mustahkamlik: 30 urug', haqiqiy ATE = 20 ===
  katak: o'rtacha xato, * - sezilarli siljish (> 2 SE), (std)
  ssenariy                   regressiya            IPW   IPW kesilgan           AIPW
  ikkala model to'g'ri      +0.2 ( 1.7)    +1.2 ( 4.2)    +3.1*( 2.1)    +0.2 ( 2.0)
  natija modeli xato       +27.4*( 2.1)    +1.2 ( 4.2)    +3.1*( 2.1)    +1.2 ( 4.2)
  propensity xato           +0.2 ( 1.7)   +34.4*( 3.9)   +32.1*( 2.4)    +0.3 ( 1.8)
  ikkalasi xato            +27.4*( 2.1)   +34.4*( 3.9)   +32.1*( 2.4)   +47.8*( 6.4)

=== 2. Kuchsiz overlap: tanlash 3 barobar kuchli (modellar to'g'ri) ===
  oddiy tanlash    maks og'irlik (mediana)     30.0, ESS ulushi 0.533
    IPW:   +1.2 ( 4.2)  IPW kesilgan:   +3.1*( 2.1)  AIPW:   +0.2 ( 2.0)
  kuchli tanlash   maks og'irlik (mediana)    385.9, ESS ulushi 0.047
    IPW:   +7.2*11.7-bob  IPW kesilgan:  +29.5*( 3.1)  AIPW:   +2.2 10.4-bob

=== 3. O'lchanmagan chalkashtiruvchi u (modellar x bo'yicha to'g'ri) ===
   u kuchi     regressiya            IPW   IPW kesilgan           AIPW
       0.0    +0.2 ( 1.7)    +1.2 ( 4.2)    +3.1*( 2.1)    +0.2 ( 2.0)
      0.25    +1.6*( 1.8)    +2.8*( 3.8)    +4.5*( 2.0)    +1.6*( 2.0)
       0.5    +5.9*( 1.8)    +7.0*( 3.8)    +8.7*( 2.1)    +5.9*( 2.0)
       1.0   +20.8*( 1.8)   +22.5*( 2.4)   +23.2*( 1.8)   +21.0*( 1.8)
  u kuchi 1.0 da hamma usul sezilarli siljigan: True
  ⭐ AIPW: bittasi to'g'ri bo'lsa yetadi; yashirin u ga qarshi - ojiz

Natija tahlili.

Bu misolda x1^2 ham kupon olishga, ham natijaga ta'sir qiladi. "To'g'ri" model x1^2 ni o'z ichiga oladi, "xato" model — yo'q (amalda: nochiziqlikni unutish). Har katak — 30 urug' bo'yicha o'rtacha xato va std.

1-bo'lim — ikki karra mustahkamlik jadvalda:

  • Ikkala model to'g'ri — regressiya va AIPW siljimagan (+0.2), IPW siljimagan, lekin std ikki barobar katta (4.2 va 2.0). Kesilgan IPW +3.1 — 2-misoldagi kabi, kesish baholanadigan kattalikni o'zgartiradi.
  • Natija modeli xato — regressiya +27.4 ga siljidi (haqiqiy ta'sirdan ham katta!). AIPW +1.2 — siljish yo'q: to'g'ri propensity natija modeli xatosini tuzatdi.
  • Propensity xato — IPW +34.4, kesilgan IPW +32.1; AIPW +0.3 — to'g'ri natija modeli uni qutqardi.
  • Ikkalasi xato — AIPW +47.8 — hammasidan yomon. Ikki karra mustahkamlik "ikkitadan bittasi" kafolati, "ikkalasi xato bo'lsa ham" emas; bu holda tuzatish hadi xatoni hatto kuchaytirishi mumkin (adabiyotda Kang va Schafer misoli bilan mashhur).

2-bo'lim — overlap. Tanlash 3 barobar kuchaytirilganda propensity 0 va 1 ga yaqinlashdi: maksimal og'irlik mediana 30.0 dan 385.9 ga, samarali namuna ulushi 0.533 dan 0.047 ga tushdi — 3000 qatordan faqat ~140 tasi "ishlayapti". IPW std 4.2 dan 11.7 ga, AIPW std 2.0 dan 10.4 ga oshdi va IPW +7.2 ga siljidi. Kesilgan IPW barqarorroq (std 3.1), lekin +29.5 ga siljigan — overlap bo'lmagan hududda ma'lumot yo'q, va hech qanday formula uni yaratib bera olmaydi. Halol yo'l: overlap hududini aniqlash va ta'sirni faqat shu hudud uchun e'lon qilish.

3-bo'lim — o'lchanmagan chalkashtiruvchi u. Hamma model x bo'yicha to'g'ri. u kuchi oshgan sari hamma usul bir xilda siljiydi: 0.25 da +1.6, 0.5 da +5.9, 1.0 da +20.8 — +23.2 (ta'sirning o'zi 20!). AIPW ning ikki karra mustahkamligi bu yerda yordam bermaydi: ikkala model ham x bo'yicha to'g'ri, lekin u ni ko'rmaydi. Va eng yomoni — hech bir diagnostika buni ko'rsatmaydi: x bo'yicha balans mukammal bo'ladi. Yagona himoya — domen bilimi (qaysi chalkashtiruvchi yetishmayapti?), sezgirlik tahlili (shu jadval — "u qanchalik kuchli bo'lsa, xulosa o'zgaradi?") va imkon bo'lsa, tajriba.

Misol 4 — Uplift modellashtirish (T/S-learner, Qini) va farqlar farqi

python
"""Kimga kupon berish (uplift, Qini) va aksiya ta'siri farqlar farqi bilan."""

import numpy as np
from sklearn.ensemble import HistGradientBoostingClassifier


def sigmoid(z):
    return 1 / (1 + np.exp(-z))


def uplift_malumot(rng, n):
    """RCT: kupon 50% tasodifiy. Ta'sir narxga sezgir va yangi mijozlarda katta."""
    sod = rng.normal(0, 1, n)
    sezgir = rng.normal(0, 1, n)
    faol = rng.normal(0, 1, n)
    yangi = (rng.random(n) < 0.3).astype(float)
    X = np.column_stack([sod, sezgir, faol, yangi])
    z0 = -1.2 + 1.0 * sod + 0.5 * faol - 0.3 * yangi
    delta = -0.1 + 0.8 * sezgir + 0.7 * yangi - 0.5 * sod
    T = rng.integers(0, 2, n)
    p0, p1 = sigmoid(z0), sigmoid(z0 + delta)
    Y = (rng.random(n) < np.where(T == 1, p1, p0)).astype(int)
    return X, T, Y, p1 - p0


def model():
    return HistGradientBoostingClassifier(max_iter=150, learning_rate=0.05,
                                          max_leaf_nodes=15, min_samples_leaf=50,
                                          random_state=0)


def qini(uplift, T, Y):
    """Qini egri chizig'i: top-k ga kupon berilsa qo'shimcha xaridlar."""
    t = np.argsort(-uplift, kind="stable")
    T, Y = T[t], Y[t]
    n1, n0 = np.cumsum(T), np.cumsum(1 - T)
    q = np.cumsum(Y * T) - np.cumsum(Y * (1 - T)) * n1 / np.maximum(n0, 1)
    k = np.arange(1, len(T) + 1)
    return float(np.mean(q - q[-1] * k / len(T))), q


def did(y, muolaja, oldin, keyin):
    """Do'kon darajasida DiD va SE (do'kon bo'yicha klaster)."""
    farq = y[:, keyin].mean(1) - y[:, oldin].mean(1)
    a, b = farq[muolaja], farq[~muolaja]
    return (a.mean() - b.mean(),
            np.sqrt(a.var(ddof=1) / len(a) + b.var(ddof=1) / len(b)))


def did_malumot(rng, parallel):
    hafta = np.arange(1, 53)
    muolaja = np.arange(24) < 12                     # 12 Samarqand do'koni
    daraja = np.where(muolaja, 80.0, 120.0) + rng.normal(0, 8, 24)
    y = (daraja[:, None] + 0.3 * hafta + 8 * np.sin(2 * np.pi * hafta / 52)
         + 12.0 * (muolaja[:, None] & (hafta >= 27))
         + rng.normal(0, 4, (24, 52)))
    if not parallel:
        y += 0.35 * hafta * muolaja[:, None]        # Samarqand tezroq o'sadi
    return y, muolaja, hafta


def main() -> None:
    rng = np.random.default_rng(0)
    Xtr, Ttr, Ytr, _ = uplift_malumot(rng, 20_000)
    Xte, Tte, Yte, tau = uplift_malumot(rng, 20_000)

    print("=== 1. Uplift: RCT ma'lumoti (o'quv 20 000, test 20 000) ===")
    ate = Yte[Tte == 1].mean() - Yte[Tte == 0].mean()
    print(f"  o'rtacha uplift: haqiqiy {tau.mean():+.4f}, test RCT bahosi "
          f"{ate:+.4f}")
    print(f"  ishontiriladigan (tau > 0.05) {np.mean(tau > 0.05):.3f}, "
          f"'bezovta qilma' (tau < -0.02) {np.mean(tau < -0.02):.3f}")
    javob = model().fit(Xtr, Ytr).predict_proba(Xte)[:, 1]
    f1 = model().fit(Xtr[Ttr == 1], Ytr[Ttr == 1]).predict_proba(Xte)[:, 1]
    f0 = model().fit(Xtr[Ttr == 0], Ytr[Ttr == 0]).predict_proba(Xte)[:, 1]
    s = model().fit(np.column_stack([Xtr, Ttr]), Ytr)
    s_up = (s.predict_proba(np.column_stack([Xte, np.ones(len(Xte))]))[:, 1]
            - s.predict_proba(np.column_stack([Xte, np.zeros(len(Xte))]))[:, 1])
    bashorat = {"tasodifiy": rng.random(len(Tte)), "javob modeli": javob,
                "S-learner": s_up, "T-learner": f1 - f0, "oracle (haqiqiy)": tau}

    print("\n=== 2. Qini: test to'plamida ===")
    k30 = int(0.3 * len(Tte))
    print(f"  {'model':<17} {'Qini maydoni':>12} {'top-30% qo_sh.':>15} "
          f"{'corr(tau)':>10}")
    for nom, u in bashorat.items():
        maydon, q = qini(u, Tte, Yte)
        print(f"  {nom:<17} {maydon:>12.1f} {q[k30 - 1]:>15.1f} "
              f"{np.corrcoef(u, tau)[0, 1]:>10.3f}")
    print(f"  hammaga kupon ({len(Tte):,} ta): qo'shimcha xaridlar "
          f"{qini(tau, Tte, Yte)[1][-1]:.1f}")

    print("\n=== 3. Juftlashgan bootstrap (200): Qini maydoni farqi ===")
    b_rng = np.random.default_rng(1)
    juftlar = [("T-learner", "S-learner"), ("T-learner", "javob modeli")]
    farqlar = {j: [] for j in juftlar}
    for _ in range(200):
        i = b_rng.integers(0, len(Tte), len(Tte))
        m = {nom: qini(bashorat[nom][i], Tte[i], Yte[i])[0]
             for nom in ["T-learner", "S-learner", "javob modeli"]}
        for a, b in juftlar:
            farqlar[(a, b)].append(m[a] - m[b])
    for (a, b), v in farqlar.items():
        v = np.array(v)
        print(f"  {a} - {b}: {v.mean():+.1f} (SE {v.std(ddof=1):.1f}), "
              f"sezilarli: {abs(v.mean()) > 2 * v.std(ddof=1)}")
    ts = np.array(farqlar[("T-learner", "S-learner")])
    if ts.mean() > 2 * ts.std(ddof=1):
        print("  tanlov: T-learner (S-learner sezilarli yomon)")
    elif ts.mean() < -2 * ts.std(ddof=1):
        print("  tanlov: S-learner (sezilarli yaxshi va soddaroq - bitta model)")
    else:
        print("  tanlov: S-learner (sodda, T dan sezilarli yomon emas)")

    print("\n=== 4. Farqlar farqi: aksiya Samarqandda 27-haftadan, haqiqiy +12 ===")
    for nom, parallel in [("parallel trendlar", True), ("trendlar farqli", False)]:
        y, muolaja, hafta = did_malumot(np.random.default_rng(5), parallel)
        oldin, keyin = hafta < 27, hafta >= 27
        oldin_keyin = (y[muolaja][:, keyin].mean()
                       - y[muolaja][:, oldin].mean())
        keyin_farq = y[muolaja][:, keyin].mean() - y[~muolaja][:, keyin].mean()
        d, se = did(y, muolaja, oldin, keyin)
        p, pse = did(y[:, :26], muolaja, hafta[:26] < 14, hafta[:26] >= 14)
        print(f"  [{nom}]")
        print(f"    naiv oldin-keyin {oldin_keyin:+6.1f}, naiv shaharlar farqi "
              f"{keyin_farq:+6.1f}")
        print(f"    DiD {d:+6.2f} (SE {se:.2f}); placebo (1-26 hafta, soxta "
              f"14-hafta) {p:+5.2f} (SE {pse:.2f})")
        print(f"    parallel trend tekshiruvi o'tdi: {abs(p) <= 2 * pse}; "
              f"DiD 2*SE ichida +12 ga: {abs(d - 12) <= 2 * se}")
    print("  ⭐ Uplift - RCT da baholanadi; DiD - placebo bilan tekshiriladi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Uplift: RCT ma'lumoti (o'quv 20 000, test 20 000) ===
  o'rtacha uplift: haqiqiy +0.0150, test RCT bahosi +0.0134
  ishontiriladigan (tau > 0.05) 0.391, 'bezovta qilma' (tau < -0.02) 0.395

=== 2. Qini: test to'plamida ===
  model             Qini maydoni  top-30% qo_sh.  corr(tau)
  tasodifiy                -20.2             8.3     -0.016
  javob modeli             -19.6            15.4     -0.023
  S-learner                464.0           593.7      0.952
  T-learner                447.0           562.6      0.917
  oracle (haqiqiy)         476.6           647.9      1.000
  hammaga kupon (20,000 ta): qo'shimcha xaridlar 135.1

=== 3. Juftlashgan bootstrap (200): Qini maydoni farqi ===
  T-learner - S-learner: -16.8 (SE 8.0), sezilarli: True
  T-learner - javob modeli: +466.2 (SE 27.4), sezilarli: True
  tanlov: S-learner (sezilarli yaxshi va soddaroq - bitta model)

=== 4. Farqlar farqi: aksiya Samarqandda 27-haftadan, haqiqiy +12 ===
  [parallel trendlar]
    naiv oldin-keyin  +10.0, naiv shaharlar farqi  -25.5
    DiD +12.77 (SE 0.52); placebo (1-26 hafta, soxta 14-hafta) -0.65 (SE 0.61)
    parallel trend tekshiruvi o'tdi: True; DiD 2*SE ichida +12 ga: True
  [trendlar farqli]
    naiv oldin-keyin  +19.1, naiv shaharlar farqi  -11.7
    DiD +21.87 (SE 0.52); placebo (1-26 hafta, soxta 14-hafta) +3.90 (SE 0.61)
    parallel trend tekshiruvi o'tdi: False; DiD 2*SE ichida +12 ga: False
  ⭐ Uplift - RCT da baholanadi; DiD - placebo bilan tekshiriladi

Natija tahlili.

1-bo'lim — RCT ma'lumoti: kupon tasodifiy berilgan. O'rtacha uplift juda kichik (+0.0150, test bahosi +0.0134) — "hammaga kupon" deyarli foyda bermaydi. Lekin o'rtacha ostida ikki qarama-qarshi guruh yashiringan: 39.1% mijozda kupon xarid ehtimolini 5 foiz punktdan ko'proq oshiradi, 39.5% ida esa kamaytiradi ("bezovta qilma" — masalan, sodiq mijoz kuponni "narx sun'iy oshirilgan" deb qabul qiladi).

2-bo'lim — Qini. Javob modeli (xarid ehtimoli yuqorilarga kupon) tasodifiydan yaxshi emas: Qini maydoni -19.6 (tasodifiy -20.2), haqiqiy uplift bilan korrelyatsiyasi -0.023. U "baribir oladiganlar" ni topadi — ular kuponsiz ham xarid qiladi. Uplift modellari esa oracle ga yaqin: S-learner 464.0, T-learner 447.0, oracle 476.6. Eng kuchli raqam — top-30% mijozga kupon bersak, qo'shimcha xaridlar S-learner bilan 593.7, hammaga kupon bersak esa atigi 135.1 — kam kupon bilan to'rt barobar ko'p natija, chunki "bezovta qilma" guruhiga kupon yuborilmaydi.

3-bo'lim — juftlashgan bootstrap. T-learner S-learner dan 16.8 ga yomon (SE 8.0) — sezilarli. Bu kutilganidan farqli bo'lishi mumkin (nazariyada S-learner uplift ni nolga "siqishi" bilan tanilgan), lekin bu ma'lumotda ta'sir silliq va kuchli, bitta model uni barcha 40 000 qatorda o'rganadi, T-learner esa har modelni yarim ma'lumotda o'qitadi va ikki xatoni qo'shadi. Qoida bo'yicha — S-learner: u ham soddaroq (bitta model), ham sezilarli yaxshiroq. Boshqa ma'lumotda natija teskari bo'lishi mumkin — shuning uchun baholash har safar RCT test to'plamida qayta o'tkaziladi.

4-bo'lim — farqlar farqi. Parallel trendlar bajarilganda: naiv "oldin-keyin" +10.0 (trend va mavsum aralashgan), naiv "Samarqand va Toshkent" -25.5 (shaharlar darajasi farqi — aksiya "zarar" qilganga o'xshaydi!). DiD +12.77 (SE 0.52) — haqiqiy +12 ga yaqin, placebo (aksiyadan oldingi davrda soxta aksiya) -0.65 (SE 0.61) — nolga yaqin, tekshiruv o'tdi. Trendlar farqli bo'lganda (Samarqand har hafta 0.35 ga tezroq o'sadi): DiD +21.87 — o'sish ta'sirga qo'shilgan, xato deyarli ikki barobar. Lekin placebo +3.90 (SE 0.61) buni oldindan ko'rsatdi: tekshiruv o'tmadi. Placebo testi parallel trendlarni isbotlamaydi (oldingi davrda parallel bo'lib, keyin ajralishi mumkin), lekin buzilganini ko'pincha ushlaydi.


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

Noto'g'ri fikr To'g'risi
"Kupon olganlar ko'p xarid qildi — demak kupon ishlaydi" Naiv farq = ta'sir + tanlash siljishi (1 va 2-misolda 3 barobar katta)
"Hamma belgini nazorat qilsak xavfsiz" Mediator ta'sirni yeydi, kollayder soxta bog'lanish yaratadi
"Katta namuna — ishonchli sababiy xulosa" Katta namuna noto'g'ri javobni tor CI bilan beradi
"ATE va ATT — bir xil narsa" Ta'sir heterogen bo'lsa farq qiladi; savolga qarab tanlanadi
"Propensity modeli yaxshi bashorat qilsa — yaxshi" Maqsad — balans va overlap, AUC emas; juda yuqori AUC — overlap yo'qligi belgisi
"IPW — eng to'g'ri usul" Ekstremal og'irliklarda juda shovqinli; balans va ESS ni tekshiring
"AIPW hamma muammoni hal qiladi" Bittasi to'g'ri bo'lsa ishlaydi; ikkalasi xato yoki yashirin U bo'lsa — yo'q
"Balans mukammal — chalkashtirish yo'q" Faqat o'lchangan X bo'yicha; U ko'rinmaydi
"Xarid ehtimoli yuqorilarga kupon beramiz" Bu javob modeli; uplift modeli kerak
"DiD har doim trendni yo'qotadi" Faqat parallel trendlar bajarilsa; placebo bilan tekshiring

6. Keng tarqalgan xatolar va yechimlari

1. Naiv farq

python
tasir = df[df.kupon == 1].xarid.mean() - df[df.kupon == 0].xarid.mean()   # ⚠️
# DAG -> nazorat to'plami -> g-hisoblash / IPW / AIPW + bootstrap CI       # ✅

2. Muolajadan keyingi belgini nazorat qilish

python
LinearRegression().fit(df[["kupon", "tashriflar", "sodiqlik"]], y)        # ⚠️ mediator
LinearRegression().fit(df[["kupon", "sodiqlik"]], y)                      # ✅ faqat oldingi belgilar

3. Overlap siz IPW

python
w = T / e + (1 - T) / (1 - e); ate = ...                                  # ⚠️
# avval: e taqsimoti guruhlar bo'yicha, maks og'irlik, ESS, SMD            # ✅

4. Bootstrap da propensity qayta o'qitilmaydi

python
e = propensity(X, T)
for _ in range(200): i = ...; ate_i = ipw(T[i], Y[i], e[i])               # ⚠️
for _ in range(200): i = ...; ate_i = ipw(T[i], Y[i], propensity(X[i], T[i]))  # ✅

5. Kesishni yashirish

python
e = np.clip(e, 0.05, 0.95)   # va hisobotda "ATE"                         # ⚠️
# "overlap hududi (0.05 < e < 0.95) uchun ta'sir" deb yozing              # ✅

6. Uplift o'rniga javob modeli

python
maqsad = model.fit(X, y).predict_proba(X)[:, 1].argsort()[::-1][:k]       # ⚠️
uplift = f1.predict_proba(X)[:, 1] - f0.predict_proba(X)[:, 1]            # ✅ + Qini (RCT da)

7. DiD da faraz tekshirilmaydi

python
did = (a_keyin - a_oldin) - (b_keyin - b_oldin)                           # ⚠️
# + placebo (oldingi davrda soxta sana), SE do'kon bo'yicha klaster        # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 4.10, 8.4-darslar (o'tilgan): Korrelyatsiya va sababiyat, Simpson paradoksi
  • 11-qism (o'tilgan): Gipoteza testlari, bootstrap 11.8-bob — CI va sezilarlilik
  • 13.3, 13.10-darslar (o'tilgan): Koeffitsientlarni talqin qilish va logistik regressiya — propensity
  • 17.8-dars (o'tilgan): Leakage — muolajadan keyingi belgilar sababiy tahlilda ham tuzoq
  • 27.13-dars (o'tilgan): A/B test — sababiy ta'sirni aniqlashning oltin standarti
  • 28.10-dars (o'tilgan): Katta ma'lumot — hajm sababni isbotlamaydi
  • 28.12-dars: Amaliyot — aksiya ta'sirini sababiy baholash (aksiya tasodifiy emas)
  • Loyihalar va karyera qismi: Mahsulot tahlili intervyularining markaziy savoli: "bu o'zgarish metrikani oshirdimi?"

8. Eng yaxshi amaliyotlar

  1. Avval savol va estimand: ATE, ATT yoki CATE?

  2. DAG ni ma'lumotga qaramasdan chizing; nazorat to'plamini orqa eshik mezoni bilan tanlang.

  3. Imkon bo'lsa — tajriba; kuzatuv tahlili tajribaning o'rnini bosmaydi.

  4. Overlap va balansni tekshiring: e taqsimoti, maks og'irlik, ESS, SMD.

  5. Bir necha usul (g-hisoblash, IPW, AIPW) — mos kelishi ishonchni oshiradi, farqi esa savol tug'diradi.

  6. Butun jarayonni bootstrap qiling; kesish bo'lsa, estimand o'zgarganini yozing.

  7. Yashirin chalkashtiruvchilar ro'yxatini va sezgirlik tahlilini hisobotga qo'shing.

  8. Uplift — RCT da baholanadi; DiD — placebo bilan tekshiriladi.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # sodiqlar kupon ko'proq olsa, naiv farq ta'sirni oshiradimi yoki kamaytiradimi?
2.  # mediatorni nazorat qilsak nima baholanadi?
3.  # faqat sharh qoldirganlarni tahlil qilish - qanday xato?
4.  # Simpson: har segmentda +3 punkt, umumiy -9 - qaysi javob to'g'ri?
5.  # kupon sodiqlarga ko'proq berilsa va ularga kam ta'sir qilsa: ATT va ATE?
6.  # IPW dan keyin |SMD| < 0.1 bo'lsa, chalkashtirish yo'qmi?
7.  # e ni [0.05, 0.95] ga kesish nimani o'zgartiradi?
8.  # AIPW: natija modeli xato, propensity to'g'ri - siljish?
9.  # AIPW: ikkalasi xato - siljish?
10. # yashirin u kuchaysa AIPW nima qiladi?
11. # javob modeli bilan tanlangan top-30% - uplift?
12. # placebo DiD 0 dan sezilarli farq qilsa?
Javoblar
  1. Oshiradi (1-misolda 94.4 va 30)
  2. Faqat bevosita ta'sir (9.1 va umumiy 40)
  3. Kollayder bo'yicha tanlash — soxta ta'sir (-14.4, haqiqiy 0)
  4. Segment kuponga sabab bo'lsa — segment ichidagi va standartlangan (+0.031)
  5. ATT < ATE (33.17 va 40.22)
  6. Yo'q — faqat o'lchangan X bo'yicha
  7. Og'irliklarni va baholanadigan kattalikni; dispersiya kamayadi, siljish paydo bo'ladi
  8. Deyarli yo'q (+1.2)
  9. Katta (+47.8) — kafolat yo'q
  10. Boshqa usullar bilan birga siljiydi (+21.0)
  11. Tasodifiydan deyarli farq qilmaydi
  12. Parallel trendlar shubhali — DiD ga ishonmang

Vazifa 2: Xatolarni tuzating

python
1.  tasir = y[kupon == 1].mean() - y[kupon == 0].mean()   # kuzatuv ma'lumoti

2.  X = df[["kupon", "tashriflar", "sharh_qoldirdi", "sodiqlik"]]

3.  e = propensity(X, T)
    ates = [ipw(T[i], Y[i], e[i]) for i in bootstrap_indekslar]

4.  maqsad = df.sort_values("xarid_ehtimoli", ascending=False).head(k)

5.  did = (sam_keyin - sam_oldin) - (tosh_keyin - tosh_oldin)   # tekshiruvsiz
Javoblar
python
1.  # DAG -> X = [sodiqlik, oldingi, faollik, shahar]; AIPW + bootstrap CI

2.  X = df[["kupon", "sodiqlik"]]         # mediator va kollayder yo'q

3.  ates = [ipw(T[i], Y[i], propensity(X[i], T[i])) for i in bootstrap_indekslar]

4.  uplift = f1.predict_proba(X)[:, 1] - f0.predict_proba(X)[:, 1]
    maqsad = df.assign(u=uplift).sort_values("u", ascending=False).head(k)

5.  # + placebo DiD oldingi davrda; SE do'kon bo'yicha klaster

Vazifa 3: DAG lar

Modellang (1-misol asosida):

  1. Teskari bog'liqlik: xarid → kupon (ko'p xarid qilganga kupon yuboriladi) — naiv farq qanday o'zgaradi?
  2. Mediator va chalkashtiruvchi birga — qaysi to'plam umumiy ta'sirni beradi?
  3. M-tuzilma: A → Z ← B, A → T, B → Y — Z ni nazorat qilish kerakmi? Simulyatsiya bilan tekshiring
  4. Simpson misolida segment kuponning natijasi bo'lsin — endi qaysi javob to'g'ri?

Vazifa 4: Usullar

Modellang (2-misol asosida):

  1. Propensity ni HistGB bilan baholang — balans va AIPW xatosi qanday o'zgaradi?
  2. Moslashtirishda caliper ni 0.05, 0.2, 1.0 qiling — siljish va dispersiya
  3. Cross-fitting bilan AIPW: ma'lumotni 2 ga bo'lib, modellarni bir yarmida o'qitib, ikkinchisida baholang
  4. ATC ni baholang va haqiqiy qiymat bilan solishtiring

Vazifa 5: Mustahkamlik

Modellang (3-misol asosida):

  1. Overlap hududini 0.05 < e < 0.95 deb, ta'sirni faqat shu hudud uchun baholang — haqiqiy qiymat ham shu hudud bo'yicha hisoblansin
  2. E-qiymat g'oyasi: u ning qaysi kuchida AIPW xulosasi "ta'sir yo'q" ga o'tadi?
  3. u ning proksisi bor (u bilan korrelyatsiyasi 0.7) — uni qo'shish siljishni qanchaga kamaytiradi?
  4. Natija modelini HistGB qiling — "natija modeli xato" ssenariysida regressiya siljishi yo'qoladimi?

Vazifa 6: Uplift va DiD

Modellang (4-misol asosida):

  1. X-learner yozing va Qini da T/S bilan juftlashgan taqqoslang
  2. Kupon narxi 15 ming so'm, xarid marjasi 60 ming so'm — foydani maksimallashtiradigan top-k ulushini toping
  3. Uplift modelini kuzatuv ma'lumotida (kupon sodiqlarga ko'proq) o'qiting — Qini RCT test da qanday?
  4. DiD da event study: har hafta uchun muolaja va nazorat farqini chizing (aksiyadan oldingi haftalar ~0 bo'lishi kerak)

Vazifa 7: O'ylash

Marketing direktori: "Bir yillik ma'lumotda kupon olganlar 60 ming so'm ko'proq xarid qilgan. Tahlilchilar buni 'chalkashtirish' deb, propensity score bilan 38 ming so'm chiqardi. Ammo men ular noto'g'ri deb o'ylayman — biz sodiqlikni ham, oldingi xaridlarni ham nazorat qildik, demak 38 ming — haqiqiy ta'sir. Keyingi yil kupon byudjetini ikki barobar oshiramiz."

Javob

Qisqa javob: 38 ming — 60 mingdan ancha ishonchliroq, lekin "haqiqiy ta'sir" deb e'lon qilish uchun ikkita savol ochiq: yashirin chalkashtiruvchi va byudjetni oshirganda kimga kupon borishi.

1. Nega 60 ming noto'g'ri. Kupon sodiq mijozlarga ko'proq berilgan — ular kuponsiz ham ko'p xarid qiladi. 2-misolda xuddi shu tuzilmada naiv farq 102.44, haqiqiy ATE esa 40.22 edi.

2. Nega 38 ming ham kafolat emas.

python
# 1) yashirin chalkashtiruvchilar: daromad, raqobatchi do'kon yaqinligi,
#    kupon yuborish qoidasi (masalan, "savat tashlab ketganlarga")
#    -> 3-misol: u kuchi 1.0 da HAMMA usul +21 ga siljidi, balans esa mukammal
# 2) overlap: kupon olmagan sodiq mijozlar bormi? ESS, maks og'irlik
# 3) estimand: 38 ming - ATE mi, ATT mi? byudjet oshsa yangi mijozlarga
#    beriladi - ularga ta'sir boshqacha (CATE)

3. Byudjetni ikki barobar oshirish haqida. Qo'shimcha kuponlar kimga boradi? Hozir olmayotgan mijozlarga — ular uchun ta'sir boshqa (ATC yoki ularning CATE si). 4-misolda o'rtacha uplift deyarli nol edi, lekin top-30% ga yo'naltirish hammaga berishdan to'rt barobar ko'p qo'shimcha xarid berdi, "bezovta qilma" guruhida esa kupon zarar qildi.

4. Taklif.

  • Keyingi 4 hafta: mijozlarning 10% ida A/B test (kupon tasodifiy) — haqiqiy ATE va CATE uchun ma'lumot.
  • Shu ma'lumotda uplift modeli (S/T-learner) va Qini bilan tanlov.
  • Byudjet "kimga" savoli hal bo'lgach oshiriladi.

Direktorga javob: "38 ming — biz o'lchagan chalkashtiruvchilarni hisobga olgan eng yaxshi baho, lekin u 'biz hamma sababni o'lchaganmiz' degan farazga tayanadi — buni ma'lumot bilan tekshirib bo'lmaydi. Byudjetni ikki barobar qilish esa boshqa savol: qo'shimcha kuponlar hozir olmayotgan mijozlarga boradi, ularga ta'sir noma'lum. Mijozlarning kichik qismida bir oylik tajriba bu savolga aniq javob beradi va kuponni kimga berish kerakligini ham ko'rsatadi — shundan keyin byudjet raqam bilan asoslanadi."

Nimani mustahkamlaydi: 2.2, 2.3, 2.6, 2.9-bo'limlar.


Xulosa

Bu darsda kuzatuv ma'lumotidan sababiy ta'sirni baholashni va uning chegaralarini o'rgandik.

Eng muhim uch fikr:

  1. Sababiy savol — dizayn masalasi, hajm emas. 1-misolda naiv farq haqiqiy ta'sirdan uch barobar katta chiqdi (94.4 va 30); mediatorni nazorat qilish umumiy ta'sirni 40 dan 9.1 ga tushirdi; kollayder bo'yicha tanlash nol ta'sirni -14.4 qilib ko'rsatdi; Simpson paradoksida umumiy jadval kuponni zararli (-0.090) ko'rsatdi, segmentlar esa foydali (+0.031). Qaysi o'zgaruvchini nazorat qilishni DAG hal qiladi, ma'lumot emas.

  2. Kuzatuv ma'lumotida — bir necha usul, overlap, balans va halol CI. 2-misolda naiv farq 30 urug'da o'rtacha +62.6 ga siljidi; regressiya, g-hisoblash, moslashtirish, IPW (ATE va ATT) va AIPW siljimadi (RMSE 2.07-4.50), kesilgan IPW esa estimandni o'zgartirib +3.69 ga siljidi. IPW dan keyin SMD 1.187 dan -0.024 ga tushdi. 3-misolda AIPW bitta model xato bo'lganda ham to'g'ri qoldi (+1.2, +0.3), ikkalasi xato bo'lganda esa +47.8 ga siljidi; kuchsiz overlap da samarali namuna 4.7% ga tushib, hamma usul shovqinli bo'ldi.

  3. O'lchanmagan chalkashtiruvchiga qarshi hech bir formula yordam bermaydi. 3-misolda yashirin u kuchaygan sari barcha usullar birga siljidi (+21 gacha — ta'sirning o'zidek), diagnostikalar esa jim edi. Shuning uchun tajriba — oltin standart, uplift modeli (4-misol: S-learner Qini 464.0, javob modeli tasodifiy darajada) RCT ma'lumotida baholanadi, DiD esa parallel trendlar bajarilganda to'g'ri (+12.77), buzilganda +21.87 beradi — va placebo testi buni oldindan ko'rsatdi.

Keyingi darsda Amaliyot: 28-qismning yakuniy loyihasi — do'konlar tarmog'i uchun talab bashorati va anomaliya monitoringi. Ko'p do'konli kunlik savdo ma'lumotini tayyorlash va sifat tekshiruvi, bazaviy, ETS va global gradient boosting modellarini rolling-origin backtest da juftlashgan taqqoslash, bashorat intervallari va zaxira qarori, aksiya ta'sirini sababiy baholash, yakuniy model paketi va monitoring rejasi.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
28.11-dars: Sababiy xulosa (causal inference) — IlmHamroh