A Nullmodell és p-érték alfejezetben a
Northstar kísérletre már kaptunk egy permutációs p-értéket, de ott a
nullmodell és a p-érték logikája volt a kérdés. Most a tesztválasztást
tesszük tudatossá. A cél ugyanaz a szintetikus kísérlet: értékeljük,
hogy a treatment ág növeli-e a conversion_7d
primer végpontot a control ághoz képest, az összes
randomizált ügyfél ITT-szemléletével, és becsüljük meg a hatás nagyságát
is.
Bemenet: lab/data/assignments.csv és
lab/data/outcomes.csv. Megfigyelési egység: egy
customer_id, egyszer mérve. A conversion_7d
értéke 0 vagy 1, a variant értéke control vagy
treatment. Ellenőrzési feltétel: a két tábla one-to-one
joinja után 300 sor legyen, a conversion_7d mindenütt
bináris, és a csoportszámok összege 300 legyen. Kimenet: abszolút és
relatív hatás, 95%-os konfidenciaintervallum (CI), választott teszt
p-értéke.
Induló becslés, mielőtt megoldanád: ha a kontrollkonverzió 20% körüli, milyen abszolút különbséget tartanál üzletileg fontosnak? A példában 10 százalékpontot használok gyakorlati küszöbnek; ehhez 300 ügyfél már csak mérsékelten érzékeny, ezért a CI is lényeges, nem csak a p-érték.
Két csoport összehasonlításánál az első kérdés nem a csomagfüggvény, hanem az adatfelvétel szerkezete. Négy oszlopba foglalva:
| Dizájn | Egység és mérés | Következmény | SciPy-függvény |
|---|---|---|---|
| Független | minden ügyfél egy ágban, egy mérés | a csoportok közötti szórást is becsülni kell | ttest_ind |
| Páros | ugyanazon egység kétszer, vagy előre párosított egységek | a páron belüli szint eltávolítható | ttest_rel |
Páros esetben a nyers adat két hosszú, egyenként elemű tömb: és . A páronkénti különbség
Itt
mértékegysége azonos az eredményváltozóéval, shape-je (n,).
A páros t-próba statisztikája
ahol
a különbségek átlaga,
a mintabeli szórás, a szabadságfok
.
Kis kézi példa: öt ügyfél várakozási ideje percben előtte
[10,12,11,14,15], utána [8,10,10,12,12]; a
különbségek [2,2,1,2,3], átlaguk 2,0. A szórásnégyzet
,
így
.
A párosítás csökkenti a párok közötti szórást, de csak akkor jogszerű,
ha a párosítás a tervezésből jön: ugyanaz az ügyfél előtte/utána, vagy
előre rögzített matched-pairs design.
A Northstar kísérletben nincs ilyen. Minden ügyfél egyszer szerepel,
és a randomizálás az ügyfelet önállóan osztja be az egyik ágba. Ha
valaki customer_id szerint rendezés után mesterségesen
párokat képezne, akkor egy nem létező páron belüli összefüggést
tételezne fel; a kapott standard error nem jelenthető ki a tényleges
mintavételi folyamatból. Ezért a helyes alap a független
összehasonlítás.
Független csoportoknál a klasszikus kétmintás t-próba feltételezi az
egyenlő varianciákat. Ezt feleslegesen szigorú feltevés; a
Welch-változat nem feltételezi ezt, és az equal_var=False
beállítással ez a biztonságos alap SciPy-ban. A becsült átlagkülönbség
standard errorja
ahol a csoport mintabeli varianciája, a csoport mérete; az SE mértékegysége megegyezik az eredményváltozóéval. A szabadságfok a Satterthwaite-közelítés:
Az eljárás feltétele: a csoportátlagok mintavételi eloszlása jól közelíthető normálissal. Ez igaz lehet nagy mellett akkor is, ha az egyedi értékek nem normálisak, mert a centrális határeloszlás az átlagra vonatkozik, nem az egyedekre (lásd Standard error és konfidenciaintervallum). Kicsi, erősen ferde mintánál viszont a Welch-teszt is félrevezető lehet.
A Northstar primer végpontja bináris. A Welch-teszt itt is kiszámítható, mert az átlag éppen az arány, de a konverziós végpontra van pontosabban illeszkedő modell is: a kétmintás arányteszt.
Legyen a control és treatment csoport mérete
és
;
a megfigyelt konverziós tömb
és
,
mindkettő shape-je (n_g,), elemei 0 vagy 1. A konverziók
száma
,
,
a becsült arányok
mindkettő a tartományban, dimenzió nélküli valószínűség. A nullhipotézis . A közös arány becslése a nullmodell alatt:
A tesztstatisztika a standardizált különbség:
A nevező a nullmodell szerinti standard error, ahol a közös -t használjuk. A kétoldali p-érték , ahol a standard normális eloszlásfüggvény (lásd Sűrűség és eloszlásfüggvény: a CDF területet ad, a farkak a túloldali területek).
Kézi kis számpélda: , konverziók 2 és 5, tehát , , különbség . A közös arány . A null-standard error
így , a kétoldali p-érték . A nagy p azt jelenti, hogy ekkora minta még egy látványos pontalakú különbséget sem tud stabilan kimutatni.
A CI nem a fenti pooled standard errort használja, hanem a két becslés saját szórását:
A 95%-os Wald-intervallum:
ahol . Emiatt a p-érték és a CI csak közelítőleg lesz konzisztens, nem matematikai azonossággal: a teszt null alatti, a CI az adott pontban becsült szórást használ.
Az érvényességi korlát: a normális közelítéshez mindkét csoportban elegendő várható konverziószám kell. Gyakorlati hüvelykujjként , , , mindegyike legalább 5, jobb esetben 10 legyen. Ha ez sérül, egzakt vagy permutációs eljárás indokolt; erre a küszöb, kalibráció és ritka esemény rész külön is visszatér.
A másik, a kísérleti dizájnhoz nagyon közel álló választás a
permutációs teszt. A Neyman-féle randomizációs nullmodellben a kapott
300 conversion_7d értéket rögzítettnek tekintjük, és csak a
variant címkéket keverjük újra. A tesztstatisztika ugyanaz
az átlagkülönbség:
Ha null hatás van, akkor egy ügyfél kimenete nem függ attól, melyik címkét kapja, ezért a címkék cserélhetők. A p-érték közelítése: 9999 újrarendezést készítünk rögzített seeddel, megszámoljuk, hányszor kapunk legalább akkora abszolút különbséget, majd
Ez a p-érték a nullmodell feltételes valószínűségeként értelmezhető, ahogy Nullmodell és p-érték bevezette. Fontos: a permutációs teszt a „nincs hatás egyetlen egységre sem” éles nullmodellt vizsgálja, és a statisztika megválasztásával irányítjuk, milyen eltérésre legyen érzékeny.
A statisztikai szignifikancia nem mondja meg, érdemes-e bevezetni a kezelést. A három leggyakoribb bináris hatásméret:
A RD közvetlenül működési kérdésekre válaszol: „100-ból hány további ügyfél konvertál?” Az RR viszont a kockázat arányos változását mutatja, és alacsony kontrollaránynál nagyobbnak tűnhet, mint a gyakorlati jelentősége indokolná. Ezért a két számot együtt, a CI kontextusában érdemes közölni.
A gyakorlati döntés nem a pontbecslésből, hanem a CI végpontjaiból olvasható: ha a CI magában foglalja a 0-t, nem tudunk null hatást kizárni; ha a CI teljes egésze a gyakorlati küszöb alatt van, akkor a hatás statisztikailag kimutatható, de üzletileg talán nem elég nagy. A nem szignifikáns eredmény eközben nem egyenértékű a null hatással, csak azt jelenti, hogy a minta nem adott elég erős bizonyítékot (lásd Nullmodell és p-érték).
A munkakönyvtár a lab projektgyökér. A kód a szállított
CSV-kből olvas, a saját gépeden fut, és semmilyen külső szolgáltatást
nem hív.
# s062_northstar_effect.py
# Futtatás: python s062_northstar_effect.py, a lab projektgyökérből.
import numpy as np
import pandas as pd
from scipy import stats
assign = pd.read_csv("lab/data/assignments.csv")
out = pd.read_csv("lab/data/outcomes.csv")
# One-to-one join: hiba lenne, ha egy ügyfél többször szerepelne.
df = assign.merge(out, on="customer_id", validate="one_to_one")
assert df.shape[0] == 300
assert set(df["conversion_7d"].unique()).issubset({0, 1})
x0 = df.loc[df["variant"] == "control", "conversion_7d"].to_numpy(dtype=float)
x1 = df.loc[df["variant"] == "treatment", "conversion_7d"].to_numpy(dtype=float)
n0, n1 = x0.size, x1.size
p0, p1 = x0.mean(), x1.mean()
rd = p1 - p0
rr = p1 / p0 if p0 > 0 else np.nan
relative_pct = (rr - 1) * 100
# Nullmodell: közös arány és pooled standard error.
p_hat = (x0.sum() + x1.sum()) / (n0 + n1)
se_null = np.sqrt(p_hat * (1 - p_hat) * (1 / n0 + 1 / n1))
z = rd / se_null
p_z = 2 * (1 - stats.norm.cdf(abs(z)))
# CI: unpooled standard error.
se_ci = np.sqrt(p0 * (1 - p0) / n0 + p1 * (1 - p1) / n1)
lo, hi = rd + stats.norm.ppf([0.025, 0.975]) * se_ci
assert lo <= rd <= hi
# Welch-teszt ugyanezen bináris értékeken, ellenőrzésként.
welch = stats.ttest_ind(x1, x0, equal_var=False)
# Permutációs teszt, rögzített seeddel.
rng = np.random.default_rng(20261020)
runs = 9999
y = df["conversion_7d"].to_numpy(dtype=float)
trl = (df["variant"] == "treatment").to_numpy()
obs = y[trl].mean() - y[~trl].mean()
null_stats = np.empty(runs)
for i in range(runs):
perm = rng.permutation(trl)
null_stats[i] = y[perm].mean() - y[~perm].mean()
count = np.sum(np.abs(null_stats) >= abs(obs))
p_perm = (count + 1) / (runs + 1)
print(f"n_control={n0}, n_treatment={n1}")
print(f"control={p0:.3f}, treatment={p1:.3f}")
print(f"RD={rd:.3f}, relative_pct={relative_pct:.1f}%")
print(f"95% CI: [{lo:.3f}, {hi:.3f}]")
print(f"z={z:.2f}, p_z={p_z:.3f}")
print(f"Welch p={welch.pvalue:.3f}, perm_p={p_perm:.3f}")A fenti kimenetet itt nem nevezem mértnek; a fönti blokk helyi futtatásban a lab CSV-jéből számol. Egy elvi vázlat, ami nem a szállított fájl tényleges eredménye: ha a kontrollarány 0,19, a treatment 0,28 lenne, akkor RD 0,09, relatív növekedés 47%, a z-teszt p-értéke 0,05 alá esik, a CI pedig 0 közeléből 0,18 felé nyúlna. A te futtatásod ettől véletlenül eltérhet.
Értelmezd egyszerre a pontbecslést, a bizonytalanságot és a szignifikanciát. A fenti illusztrációban az abszolút hatás 9 százalékpont, a relatív növekedés mintegy 47%. Ez önmagában fontos lehet, de a 95%-os CI széles: ha az alsó végpont 0 közelében van, akkor a valódi hatás lehet nagyon kicsi is. A p-érték így nem „a feltétel fontosságának” mértéke, hanem annak a valószínűsége, hogy a nullmodell ilyen szélsőséges statisztikát generáljon.
Kézi kontrollok:
x0.size + x1.size == 300 és
df.shape[0] == 300;conversion_7d értékkészlete csak
{0,1};lo <= rd <= hi;A validate="one_to_one" hívás az IDE, típusinformáció és hibakeresés alfejezetben
tanult ellenőrzési szemléletet alkalmazza: a join előtti elvárást
rögzítjük, nem printeljük ki utána a teljes táblát.
Értelmezési határ: a teszt kiválasztása előtt ellenőrizni kell a kimenet skáláját, a megfigyelések függőségét, a kiosztási egységet és a tervezett estimandot. A statisztikailag szignifikáns eltérés nem azonos a gyakorlati fontossággal; a százalékpontos hatás és az intervallum mindenképp szerepeljen.
A tesztválasztást a lehetséges erő is irányítja. Kis bináris szimulációval gyorsan átérezhető, mekkora kell egy adott hatáshoz:
def power_sim(n, p0, p1, runs=500, seed=123):
rng = np.random.default_rng(seed)
rej = 0
for _ in range(runs):
c = rng.binomial(n, p0)
t = rng.binomial(n, p1)
p_hat = (c + t) / (2 * n)
se = np.sqrt(p_hat * (1 - p_hat) * 2 / n)
z = ((t - c) / n) / se
p = 2 * (1 - stats.norm.cdf(abs(z)))
rej += p < 0.05
return rej / runsA power_sim(150, 0.20, 0.30) például azt kérdezi, hogy
150-150 fős ágak esetén milyen gyakran utasítjuk el a nullmodellt, ha a
valódi arányok 0,20 és 0,30. Elvi várakozásként a 0,07-es null-standard
error mellett
,
így a szimulált erő 0,4–0,5 körüli lehet; ez szimulációs becslés, nem
levezetett mért eredmény. A Power, mintanagyság és
többszörös próbák ezt a kérdést szisztematikusan tárgyalja.
A leggyakoribb tévedés az, hogy a nemparaméteres vagy rangtesztet automatikusan az átlagkülönbség „eloszlásmentes” vizsgálataként használjuk. A Mann-Whitney-féle teszt nem az átlagok egyenlőségét, hanem a két eloszlás sztochasztikus egyenlőségét vizsgálja. Ellenpélda-generátor, amely ezt a különbséget szemlélteti:
rng = np.random.default_rng(7)
# Két eloszlás, mindkettő várható értéke 1.0,
# de a variancia 4.0 és 1.0, és az alak is erősen eltér.
x = rng.gamma(shape=0.5, scale=2.0, size=400)
y = rng.exponential(scale=1.0, size=400)
p_mw = stats.mannwhitneyu(x, y, alternative="two-sided").pvalueA konkrét p-érték mintánként változik, ezért nem közlök számszerű eredményt. A vizsgálandó állítás konceptuális: a teszt ismételt mintavételben gyakran szignifikáns lehet, miközben mindkét sokasági átlag azonos. A tanulság: mindig a célparaméterhez válassz tesztet. Ha a kísérleti döntés célja az átlagkülönbség, akkor a Welch- vagy a permutációs átlagteszt a közvetlen válasz; ha a cél a teljes eloszlás különbözősége, akkor a rangpróba is indokolt lehet.
Önellenőrző kérdések:
1. Értelmezés. Egy csapattárs azt javasolja, hogy
rendezd customer_id szerint a 300 ügyfelet, majd az egymás
mellé kerülő control-treatment sorokra futtass
ttest_rel-et. Miért hibás ez a dizájn? Mit tételez fel a
párosítás, és mi a helyes eljárás itt? Rövid indoklást adj a Northstar
adatszerződéséből.
2. Implementálás. Egy cikk példájában 150-150 ügyfél
volt: control ágon 30, treatment ágon 45 konverzió. Kézzel számold ki a
z-tesztet: közös arány, null-standard error, z, kétoldali p-érték,
valamint a 95%-os Wald-CI. Ezután írj egy olyan ellenőrző
assert blokkot, amely a fenti
s062_northstar_effect.py logikájával reprodukálja ezeket a
számokat.
3. Hibadiagnózis. Az alábbi kód két hibát tartalmaz:
se = np.sqrt(p0 * (1 - p0) / n0 + p1 * (1 - p1) / n1)
z = (p1 - p0) / se
p = stats.norm.cdf(abs(z))Az előző feladat adataival mi a két hiba? Számítsd ki a hibás és a helyes p-értéket is, és magyarázd el, miért nem a CI standard errorjából készül a teszt.
4. Megváltozott feltétel. A termékcsapat egy 20-20 fős pilotban 2 control- és 7 treatment-konverziót látott. Kiszámolnád-e a z-tesztet? Ellenőrizd a normális közelítés feltételét, és javasolj alternatívát. A z-teszt számértékét add meg, de a végső döntési javaslatodban indokold, miért nem érdemes önmagában erre hagyatkozni.
1. A Northstar kísérletben nincs „ugyanaz az ügyfél
kétszer” megfigyelés, és nincs előre párosított hozzárendelés sem. A
customer_id rendezés szerinti párosítás mesterséges: a
párok tartalma attól függ, milyen sorrendet választunk. A
ttest_rel a
párkülönbségek szórását becsülné, de itt nincs közös párcsatorna,
amelyet ki lehetne vonni. A helyes választás az egy ügyfél egy
megfigyelés függetlenséget tiszteletben tartó Welch-próba vagy kétmintás
arányteszt. Ellenőrző invariant: a Northstar assignments és
outcomes táblák one-to-one joinnál 300 sort adnak, és
egyetlen ügyfélnek sincs két ághoz tartozó sora.
2. , , , közös arány . A null-standard error:
Ebből , a kétoldali p-érték
A CI standard errorja
így a 95%-os CI
,
azaz
.
A kódban a tűrés például abs(se_null - 0.05) < 1e-9 és
abs(p - 0.0455) < 1e-2.
3. Az első hiba: a teszt a nullmodell alatt közös
arányt kell használjon, ezért se nem a két külön arányból,
hanem a pooled
-ből
számolandó. A második hiba: a stats.norm.cdf(abs(z)) a
egyoldali területet adja, ami a példában 0,93 körüli érték, nem pedig a
szélsőséges farok valószínűsége; a helyes kétoldali p
.
Az 1. feladat adatain a hibás p 0,93, a helyes p kb. 0,16. A CI valóban
unpooled SE-t használ, de az a CI céljára való, nem a null alatti
p-értékre.
4. A z-teszt számítása: közös arány
,
null-standard error
,
a különbség
,
ezért
és
.
Ugyanakkor a várható cellaértékek 4,5 körüliek, tehát a normális
közelítés feltétele nem teljesül. Az ilyen kis mintánál permutációs
teszt vagy Fisher-egzakt teszt javasolt, például
stats.fisher_exact([[2, 7], [18, 13]], alternative="two-sided");
a döntéshez a pontos p-értéket és a CI-t, nem a z-próba egyetlen számát
használd. Ráadásul 20-20 fővel egy 25 százalékpontos mintabeli különbség
is nagyon bizonytalan: a CI alsó vége könnyen negatív lehet.
A tesztet először a megfigyelési terv határozza meg: páros mérés esetén párkülönbségekből számolunk, független randomizálásnál viszont a Northstar ügyfelei egyenkénti egységek, ezért párosítás hamis lenne. Független csoportokon a Welch-próba nem tételezi fel az egyenlő varianciát; bináris kimenetre a kétmintás arányteszt használ pooled null-standard errort, a CI viszont unpooled szórást. A permutációs teszt a randomizált hozzárendelés nullmodelljét közvetlenül gyakorolja. A hatást ne csak p-értékkel, hanem abszolút kockázatkülönbséggel, relatív kockázattal és a CI végpontjaival jelentsd. A nemparaméteres teszt nem automatikus átlagteszt; a célparaméterhez kell választani. A következő lépés a Power, mintanagyság és többszörös próbák alfejezetben az erő, mintanagyság és többszörös próbák rendszerezése.