A Northstar assignments.csv és outcomes.csv
fájl egy mesterséges, ügyfélszinten randomizált kísérletet ad. Minden
customer_id egy megfigyelési egység, és minden randomizált
ügyfél a control vagy a treatment karhoz
tartozik. A primer végpont a conversion_7d bináris kimenet:
az ügyfél a randomizálást követő 7 napos ablakban konvertált-e. A
kísérleti állítás az intention-to-treat (ITT) hatás: a treatment és a
control kar konverziós arányának különbsége.
A mostani ellenőrző pont feladata egy teljes, reprodukálható következtetés elkészítése, nem egyetlen p-érték leolvasása. Ehhez a következő elemeket ragadjuk össze:
Induló becslés a kód előtt. A generáló folyamatból tudjuk, hogy a szimulációs hatás 0,10, a kiinduló konverziós szint pedig az üzleti szegmens miatt kevert, nagyjából 0,18 és 0,28 körül mozoghat. Ha mindkét karban ügyfél van, akkor a kockázatkülönbség standard hibája nagyjából
Egy 95%-os intervallum tehát körülbelül széles lesz a pontbecslés körül. A tényleges mintából kapott becslés a véletlen miatt eltérhet ettől. A feladat az, hogy a kapott becslést, intervallumot és p-értéket együtt használjuk, ne külön-külön vonjunk le következtetést.
Az assignments tábla minden sora egy randomizált ügyfél,
ezért a resampling és a statisztikai következtetés egysége is az ügyfél.
Az outcomes tábla ugyanezen ügyfelek teljesen megfigyelt
kimenetét tartalmazza. A két táblát customer_id-re
egy-az-egyhez joinoljuk; ha véletlenül az orders vagy az
order_items táblát is becsatlakoztatnánk, akkor a sorok
száma többszöröződne, és az ügyfélszintű varianciát hibásan becsülnénk.
Ezért a join után explicit ellenőrzést végzünk: az összefűzött táblának
pontosan 300 sora van.
A treatment és a control kar konverziós valószínűségét -gyel és -lal jelöljük. A becsült kockázatkülönbség (effect):
ahol a treatment karbeli konverziós arány, a control karbeli. A mértékegysége százalékpont: ha például és , akkor , azaz 10 százalékpontos növekedés. Ez nem relatív kockázat: a relatív kockázat lenne, ami nem ugyanaz az állítás.
Két független binomiális arány különbségének Wald-féle standard hibája:
ahol és a két kar elemszáma. A 95%-os Wald-intervallum:
ahol a standard normális eloszlás 97,5%-os kvantilise. Ez az intervallum a becslés mintavételi bizonytalanságát fejezi ki. A Predikciós, paraméter- és Monte Carlo intervallum alfejezetben tanult értelmezés szerint a 95% az eljárás ismételt mintavételi lefedettségére vonatkozik, nem arra, hogy a paraméter 95% valószínűséggel az adott intervallumban van.
A Wald-közelítés kis mintán vagy extrém arányoknál megbízhatatlan lehet. Ezért a példában ügyfélszintű, rétegzett percentile bootstrapot is futtatunk. Ez nem egy másik elméleti feltevésre cseréli a normális közelítést, hanem ugyanazt a mintavételi bizonytalanságot közelíti szimulációval. A bootstrapnél a resampling egysége az ügyfél, és a treatment-hozzárendelést az ügyféllel együtt tartjuk meg: a control és treatment indexekből külön-külön húzunk visszatevéssel, mindig az eredeti karméretet megtartva.
A nullhipotézis , azaz a két kar konverziós aránya azonos. A Wald-féle tesztstatisztika:
és a kétoldalas p-érték
ahol a standard normális eloszlású valószínűségi változó a nullmodell szerint. A p-érték annak a feltételes valószínűsége, hogy fennállása mellett legalább ekkora abszolút különbséget látunk; nem annak a valószínűsége, hogy igaz. A Nullmodell és p-érték alfejezetben már láttuk ezt a különbséget szimulációval.
A Power, mintanagyság és többszörös próbák alfejezetben egy , per kar elrendezésre a 0,10-es hatás kimutatásának powerét körülbelül 0,54-nek becsültük, és a 80%-os powerhez tartozó minimum detectable effect (MDE) nagyjából 0,14 volt. Ez azt jelenti: ha a valódi hatás csak 0,10, akkor egyetlen ilyen kísérlet nagyjából az esetek felében nem ad szignifikáns eredményt 5%-os szinten. Ez a tervezési információ a kísérlet elvégzése előtt hasznos. Az adatok megérkezése után az úgynevezett observed power, vagyis a megfigyelt hatásból visszaszámolt erő félrevezető, ezért nem közöljük következtetésként.
Az eredményközlésnek a becslést és a bizonytalanságot kell hangsúlyoznia. A „nem szignifikáns” eredmény azt jelenti, hogy a nullmodell nem zárható ki a megfigyelt adattal; nem jelenti automatikusan, hogy a hatás nulla, vagy hogy a kezelések egyenértékűek.
Az ellenőrző pont azt kéri, hogy a következtetést több mintavételi feltételre vonatkozó érzékenységi vizsgálat kísérje. Az érzékenységi elemzés nem új elsődleges hipotézis, hanem annak ellenőrzése, hogy a primer következtetés mennyire stabil. Ha mégis több alcsoportra vagy több kimenetre futtatunk p-értéket, akkor a családi hibaarányt korrigálni kell. A legegyszerűbb Bonferroni-korrekcióval az egyes tesztekre előírt küszöb , ahol a párhuzamosan vizsgált állítások száma. A fő primer végpontot nem korrigáljuk; a feltáró alcsoport-eredményeket korrigálva, és hipotézisgenerálóként közöljük.
A munkapélda a lab/ projektgyökérben futtatható notebook
cellájaként értendő. A lab/pyproject.toml alap numerikus
függőségei tartalmazzák a pandas, NumPy és SciPy csomagokat; a kernelnek
a projekt .venv-jéhez kell kapcsolódnia (I. ellenőrző pont: megismételhető kutatói
munkaasztal). A fájlok a lab/ könyvtárban vannak, ezért
a notebook munkakönyvtárát a projektgyökérre állítjuk.
import pandas as pd
import numpy as np
from scipy import stats
rng = np.random.default_rng(20261020)
# Betöltés és egy-az-egyhez join
assign = pd.read_csv("lab/assignments.csv")
outcomes = pd.read_csv("lab/outcomes.csv")
df = assign.merge(outcomes, on="customer_id", validate="one_to_one")
# ITT nevező: minden randomizált ügyfél
assert len(df) == 300
y = df["conversion_7d"].to_numpy(dtype=float)
treat = (df["variant"] == "treatment").to_numpy()
n1 = int(treat.sum())
n0 = int((~treat).sum())
p1 = y[treat].mean()
p0 = y[~treat].mean()
delta = p1 - p0
se = np.sqrt(p1 * (1 - p1) / n1 + p0 * (1 - p0) / n0)
z_low = stats.norm.ppf(0.025)
ci_wald = (delta + z_low * se, delta - z_low * se)
z_stat = delta / se
p_value = 2 * stats.norm.sf(abs(z_stat))
print(f"n_control={n0}, n_treatment={n1}")
print(f"p_control={p0:.3f}, p_treatment={p1:.3f}")
print(f"RD={delta:.3f}, SE={se:.3f}")
print(f"95% Wald CI=({ci_wald[0]:.3f}, {ci_wald[1]:.3f})")
print(f"z={z_stat:.3f}, p={p_value:.4f}")A fenti kód kimenete a tényleges adatgenerálástól függ, ezért itt csak szemléltető példát adunk; a könyvgenerálás során ezt a cellát nem futtattuk le. Egy lehetséges futás lehet:
n_control=150, n_treatment=150
p_control=0.207, p_treatment=0.300
RD=0.093, SE=0.049
95% Wald CI=(0.038, 0.148)
z=1.883, p=0.0597
Ez a szemléltető kimenet nem a mellékelt assignments.csv
és outcomes.csv garantáltan mért eredménye, csak a generáló
folyamat körüli várakozást mutatja.
A bootstrap intervallum a Wald-féle normális közelítés ellenőrzésére:
idx0 = np.flatnonzero(~treat)
idx1 = np.flatnonzero(treat)
B = 10_000
boot_diff = np.empty(B)
for b in range(B):
s0 = rng.choice(idx0, size=n0, replace=True)
s1 = rng.choice(idx1, size=n1, replace=True)
boot_diff[b] = y[s1].mean() - y[s0].mean()
ci_boot = np.percentile(boot_diff, [2.5, 97.5])
print(f"percentile bootstrap CI=({ci_boot[0]:.3f}, {ci_boot[1]:.3f})")Az érzékenységi elemzés egyik feltétele, hogy csak a consumer szegmenst vizsgáljuk. Ez alcsoport-elemzés, ezért ha több alcsoport is lefut, akkor Bonferroni-korrekciót alkalmazunk, és feltáró eredményként közöljük.
# Érzékenység: csak consumer szegmens
mask_consumer = (df["segment"] == "consumer").to_numpy()
y_c = y[mask_consumer]
treat_c = treat[mask_consumer]
n1c = int(treat_c.sum())
n0c = int((~treat_c).sum())
p1c = y_c[treat_c].mean()
p0c = y_c[~treat_c].mean()
delta_c = p1c - p0c
se_c = np.sqrt(p1c * (1 - p1c) / n1c + p0c * (1 - p0c) / n0c)
ci_c = (delta_c + z_low * se_c, delta_c - z_low * se_c)
p_c = 2 * stats.norm.sf(abs(delta_c / se_c))
print(f"consumer-only: n_control={n0c}, n_treatment={n1c}")
print(f"RD={delta_c:.3f}, 95% CI=({ci_c[0]:.3f}, {ci_c[1]:.3f}), p={p_c:.4f}")A power nem utólagos kérdés, de a tervezési háttér reprodukálásához egy rövid szimulációs ellenőrzést is futtatunk. Ez a Power, mintanagyság és többszörös próbák alfejezetben bemutatott módszer, itt csak a konkrét elrendezést ismételjük meg röviden:
# Tervezési power-szimuláció: p0=0.18, p1=0.28, n=150 per kar
B_sim = 2_000
sim_reject = np.empty(B_sim, dtype=bool)
for i in range(B_sim):
y0 = rng.binomial(1, 0.18, 150)
y1 = rng.binomial(1, 0.28, 150)
m0 = y0.mean()
m1 = y1.mean()
sim_se = np.sqrt(m1 * (1 - m1) / 150 + m0 * (1 - m0) / 150)
sim_reject[i] = np.abs((m1 - m0) / sim_se) > stats.norm.ppf(0.975)
power_hat = sim_reject.mean()
power_se = np.sqrt(power_hat * (1 - power_hat) / B_sim)
print(f"becsült power={power_hat:.3f}, MC SE={power_se:.3f}")A szimulációs arány Monte Carlo standard hibája a Predikciós, paraméter- és Monte Carlo intervallum alfejezetben tanult képletből adódik; ez a numerikus hiba, nem a statisztikai becslés hibája.
A kód után nem elég egy számot kiírni. A következő ellenőrző invariánsokat nézzük meg:
validate="one_to_one" opciója garantálja, hogy
nincs sokszorozódás.y numpy tömb, hossza
300, értékei 0 vagy 1; treat boole-tömb, hossza 300.n0 + n1 == 300; a control
és treatment kar mérete egyenlő, mert a randomizálás 150-150 ügyfelet
osztott be.p0=0.207, p1=0.300, akkor a SE kézi
számolással közelítőleg 0,049, ami a kód kimenetével egyezik.A kapott p-érték értelmezése a szemléltető kimenetnél: , tehát 5%-os szinten nem szignifikáns a kétoldalas Wald-teszt. Ez azonban nem azt jelenti, hogy a hatás nulla. A CI nagyjából 0,04 és 0,15 közötti kockázatkülönbséget enged meg; a 0,10-es gyakorlati hatás is benne van. Az adatok alapján tehát nem zárhatjuk ki a kezelés értékes hatását, de nem is erősítik meg 5%-os szinten. A helyes állítás így hangozhat: „A becsült kezelési hatás 9,3 százalékpont (95% CI: 3,8–14,8); a nullhipotézis 5%-os szinten nem utasítható el (p = 0,06). Az intervallum széles, és a gyakorlatilag releváns 10 százalékpontos hatás nem zárható ki.”
Leadandó bizonyíték: a primer estimand, a randomizálási egység, a kizárások és hiányzó eredmények kezelése, az előre megadott alfa, a hatásméret és konfidenciaintervallum, valamint az esetleges protokolleltérések együtt szükségesek. A „nincs szignifikáns eltérés” nem bizonyít egyenértékűséget.
Egy másik elemzésben a kezelési hatás becslése , a 95%-os CI , a kétoldalas p-érték . Írja le korrekt statisztikai mondattal az eredményt, és magyarázza el, miért hibás az a következtetés, hogy „a kampány nem javítja a konverziót, mert p > 0,05”.
Két független csoportban
és
megfigyelés van, a megfigyelt konverziós arányok
és
.
Számolja ki papíron: a kockázatkülönbséget, annak standard hibáját, a
Wald-féle z-tesztstatisztikát, a kétoldalas p-értéket és a 95%-os
konfidenciaintervallumot. Ellenőrizze a p-értéket SciPy
stats.norm.sf függvényével, és adja meg a végső
mondatot.
Valaki a következő bootstrap kódot javasolja a kockázatkülönbséghez:
idx_all = np.arange(len(y))
boot_bad = np.empty(10_000)
for b in range(10_000):
s = np.random.default_rng(b).choice(idx_all, size=len(idx_all), replace=True)
y_b = y[s]
t_b = treat[s]
boot_bad[b] = y_b[t_b].mean() - y_b[~t_b].mean()Magyarázza el, miért hibás ez a mintavételi eljárás a Northstar kísérletre. Milyen statisztikai tulajdonságot sért, és hogyan javítaná a kódot? Ne futtassa a hibás kódot, elég az indoklás.
A csapat három region-alcsoportot is le akar tesztelni. Ha mindhárom alcsoportban 5%-os szinten, függetlenül futtatnak kétoldalas z-próbát, mennyi a családi hibaarány annak az eseményére, hogy legalább egy hamis pozitív eredmény van, feltéve, hogy minden nullhipotézis igaz? Számítsa ki, majd adja meg a Bonferroni-korrigált küszöböt. Ha az egyik alcsoportban a p-érték 0,02, szignifikáns-e a korrigált eljárásban?
Az 5. feladat opcionális, de hasznos ellenőrzése a tervezési gondolkodásnak. Vegyen ügyfelet karonként, kiinduló konverziós arányt , treatment arányt . Futtasson szimulációt, mindegyikben kétmintás Wald z-próbával, 5%-os kétoldalas szinten. Adja meg a becsült powert és annak Monte Carlo standard hibáját. Ha a becsült power 0,80 alatt van, hány ügyfélre lenne szükség karonként ahhoz, hogy a 0,10-es hatást 80%-os powerrel kimutassuk a normál közelítés szerint? Használja a Power, mintanagyság és többszörös próbák képletét:
A korrekt állítás: „A becsült kezelési hatás 6 százalékpont (95% CI: −4 és 16 százalékpont). A nullhipotézis 5%-os szinten nem utasítható el (p = 0,23). Az intervallum tartalmazza a nullát, de a 10 százalékpontos gyakorlati célhatást is, tehát az adatok nem bizonyítják a hatás hiányát, csak nem elég erősek az egyértelmű kimutatáshoz.” Az a kijelentés, hogy „a kampány nem javít”, azért hibás, mert a p-érték nem a nullhipotézis igazságának valószínűsége, és a nem szignifikáns eredmény nem egyenértékűségi bizonyíték. A 0,10-es hatás benne van a CI-ben, tehát az adatok összeegyeztethetők egy gyakorlatilag fontos kezelési hatással is.
A kockázatkülönbség:
A standard hiba:
A z-érték:
A kétoldalas p-érték:
A 95%-os CI:
tehát . A SciPy-ellenőrzés:
from scipy import stats
z = 0.06 / 0.0578
p = 2 * stats.norm.sf(abs(z))
print(p)A hozzávetőleges p = 0,298 nem szignifikáns; az intervallum tág, és tartalmazza a 0-t és a 10 százalékpontos hatást is.
A hibás kód egyetlen közös indexhalmazt használ, és a resample után újra felosztja a megfigyeléseket treatment és control csoportra. Ezzel két probléma van:
A helyes kód a két kart külön indexeli, és minden iterációban külön-külön húz visszatevéssel:
idx0 = np.flatnonzero(~treat)
idx1 = np.flatnonzero(treat)
for b in range(B):
s0 = rng.choice(idx0, size=len(idx0), replace=True)
s1 = rng.choice(idx1, size=len(idx1), replace=True)
boot_diff[b] = y[s1].mean() - y[s0].mean()Ha a három teszt független, és minden nullhipotézis igaz, akkor annak valószínűsége, hogy legalább egy hamis pozitív eredmény van:
A Bonferroni-korrekció a családi hibakorlátot úgy tartja -ön, hogy az egyes tesztekre küszöböt ír elő. Ezért a p = 0,02 a korrigált eljárásban nem szignifikáns. A csapatnak előre specifikálnia kell a primer végpontot, és az alcsoport-elemzést feltáró, hipotézisgeneráló eredményként korrigálva közölnie.
A Power, mintanagyság és többszörös próbák alfejezetből vett képlettel és , ezért a szükséges minta karonként:
A szögletes zárójel értéke . A számláló:
Osztva -gyel: , tehát karonként körülbelül 275 ügyfél kell ahhoz, hogy a 0,10-es hatást 80%-os powerrel kimutassuk. Ez fontos üzenet: a 150-es karméret ehhez a hatáshoz kevés. A szimulációval becsült power nagyjából 0,54, MC standard hibája , tehát a numerikus hiba kicsi a statisztikai bizonytalansághoz képest.
A leggyakoribb tévedés a nem szignifikáns eredmény egyenértékűségi bizonyítékként való kezelése. Ha például , CI és , akkor az adat nem zárja ki sem a nullát, sem a 10 százalékpontos hatást. A p-érték csak annyit mond: a nullmodell szerint nem szokatlan ekkora vagy nagyobb eltérés. A gyakorlati döntéshez a hatásméretet és a CI-t is kell nézni, továbbá a kísérlet előre tervezett powerét. Ha a kísérlet gyenge volt, akkor a nem szignifikáns eredmény várható, és nem jelent semmiféle bizonyítékot a hatás hiánya mellett. Ez a gondolat visszavezet a Predikciós, paraméter- és Monte Carlo intervallum intervallumértelmezéséhez: a CI a mintavételi bizonytalanságot adja meg, nem a paraméter elhelyezkedésének valószínűségét.
order_items táblát becsatlakoztatni
a kísérleti elemzésbe?Ezeket a kérdéseket a Megoldások és indoklás fenti ellenőrző pontjai alapján lehet megválaszolni; a legfontosabb, hogy minden statisztikai állítás mögött meg tudjuk nevezni a mintavételi egységet, a feltevést és a bizonytalansági forrást.
A IV. ellenőrző pont a hipotézisvizsgálati fogalmakat egyetlen tudományos munkamenetté fűzi össze. A Northstar-kísérletnél ügyfélszintű ITT-hatást becsülünk, Wald-standard hibával és rétegzett bootstrap intervallummal, kétoldalas nullteszttel. A p-értéket feltételes szélsőségként értelmezzük, a power pedig tervezési háttér marad: a 0,10-es hatáshoz 150 ügyfél karonként csak körülbelül 0,54-es erőt ad, ezért a nem szignifikáns eredmény nem egyenértékűségi bizonyíték. Az érzékenységi alcsoport-elemzéseket Bonferroni-korrekcióval, feltáró jelleggel közöljük. Következő lépésként a statsmodels eszközeivel ugyanezt a következtetési logikát regressziós modellekbe ültetjük át.