18.2 Randomizálás és kísérleti protokoll

18.2.1 Cél és konkrét helyzet

Az Oksági cél és megfigyelési torzítás alfejezetben azonosítottuk a zavaró változókat és azt, hogy megfigyeléses adatból csak erős feltételezésekkel lehet oksági hatást becsülni. Most a Northstar kezdőlabor lab/assignments.csv és lab/outcomes.csv fájljaira építünk egy előre rögzített kísérleti protokollt. A bemenet ügyfélszintű: a 300 ügyfél 2025-07-01-én kapott control vagy treatment hozzárendelést, majd mindenkinél ugyanazt a 7 napos kimenetet figyeltük meg. A primer endpoint a conversion_7d (0/1), az elemzési egység az ügyfél, a cél az átlagos kezelési hatás (average treatment effect, ATE) becslése.

Előzetes becslés: ha a kontroll-konverzió 0.24, a kezelési hatás 0.10, és fejenként 150-150 ügyfél van, akkor a normal approximation standard error nagyjából

0.24⋅0.76150+0.34⋅0.66150≈0.052. \sqrt{\frac{0.24 \cdot 0.76}{150} + \frac{0.34 \cdot 0.66}{150}} \approx 0.052.

A 95%-os konfidenciaintervallum szélessége tehát kb. 0.21; a 0.10-es hatás nem lesz élesen kimutatható. Ezért a protokollt becslési és döntési keretként írjuk elő, nem pedig „biztos nyertesként”.

18.2.2 Fogalmi magyarázat

18.2.2.1 A randomizálás egysége

A randomizálás egysége (unit of randomization) az a megfigyelési egység, amelyet véletlenszerűen rendelünk valamelyik karba. Itt ez a customer_id. Azért nem a rendelés vagy a tételsor, mert ugyanazon ügyfél rendelései és tételei korrelálnak: ha egy ügyfelet kezelünk, az összes rendelésére hatással lehetünk. A lab/order_items.csv tehát nem szaporíthatja fel a mintanagyságot; a kísérleti nevező továbbra is 300 ügyfél.

Kis kézi kontroll: ha 10 ügyfélből 5-öt sorsolunk treatmentbe, a randomizálási egységek száma 10. Ha minden ügyfélnek átlagosan 4 rendelési tétele van, attól még a független kísérleti egység nem 40, mert a 4 tétel egy közös sorsolás következménye.

18.2.2.2 Exposure és ITT

Az exposure a tényleges beavatkozásnak való kitettség, például az ajánlat megjelenése, az email megnyitása vagy a push értesítés kézbesítése. A randomizálás a szándékolt hozzárendelést, a variant oszlopot adja; a tényleges kitettség ettől eltérhet.

Az intention-to-treat (ITT) elv szerint minden ügyfelet abban a karban elemzünk, ahová randomizálták, függetlenül attól, hogy a kezelést ténylegesen megkapta-e. Ez megőrzi a randomizálás előnyét: a két kar összehasonlíthatóságát. Ha csak a ténylegesen kitett ügyfeleket vizsgálnánk, a kitettség maga is lehet a kezelés következménye, és újra megjelenhet a zavaró hatás.

A laborban az ITT nevező az összes randomizált ügyfél: mind a 300 sor. A primer becslés a treatment és a control kar conversion_7d átlagának különbsége.

18.2.2.3 Guardrail

A guardrail őrszem-mutató: olyan előre rögzített másodlagos kimenet, amelynek romlása esetén a bevezetést leállítjuk, még akkor is, ha a primer mutató javul. A mi protokollunkban a revenue_7d_minor ilyen: ha a treatment kar átlagos 7 napos bevétele több mint 5%-kal a kontroll alá esik, a döntés block.

Fontos, hogy a guardrail szabályát az adatok megtekintése előtt rögzítsük. Utólag választott guardrail esetén bármilyen véletlen ingadozást „kockázatnak” nevezhetnénk, és a döntés nem lenne reprodukálható.

18.2.2.4 Attrition

Az attrition az ügyfelek kiesése a megfigyelési ablakból: hiányzó kimenet, kilépés, adatvédelmi tiltakozás. A Northstar laborban nincs ilyen: minden randomizált ügyfélhez tartozik teljes, fix ablakú outcomes.csv sor. A protokollban mégis előírjuk a kezelést: ha egy conversion_7d hiányozna, az ügyfél az ITT nevezőben marad, a hiányzó konverziót 0-ként imputáljuk, és a döntés mellett hiányzási riportot készítünk. Ez konzervatív szabály; más imputáció érzékenységvizsgálatként szerepelhet.

18.2.2.5 Interference

Az interference azt jelenti, hogy egy ügyfél kimenete függ más ügyfelek hozzárendelésétől. A randomizált kísérlet standard becslője feltételezi a SUTVA feltevést (stable unit treatment value assumption): minden egység potenciális kimenete csak a saját hozzárendelésétől függ. Ha például a kezelés egy nyilvános weboldali kedvezmény, a kontroll ügyfelek is láthatják, így a kontroll kimenete már nem a tiszta Y(0)Y(0).

A mi mesterséges laborunkban a kezelés ügyfélszintű és elszigetelt; a protokollban ezt feltevésként rögzítjük, és a kísérleti jelentésben megnevezzük mint érvényességi korlátot.

18.2.3 Matematikai híd: függetlenség, hatásméret és erő

A randomizálás legfontosabb következménye a függetlenség:

A⊥⊥(Y(0),Y(1),X), A \perp\!\!\!\perp (Y(0), Y(1), X),

ahol A=1A=1 treatment, A=0A=0 control, Y(0)Y(0) és Y(1)Y(1) a potenciális kimenetek, XX pedig minden, a randomizálás előtt mért változó. Emiatt nincs szükség zavaró változóra való kiigazításra; az egyszerű átlagkülönbség torzítatlanul becsli az ATE-t:

Δ=E[Y(1)]−E[Y(0)]. \Delta = E[Y(1)] - E[Y(0)].

A mintabeli becslő:

Δ̂=Y‾1−Y‾0, \hat{\Delta} = \bar{Y}_1 - \bar{Y}_0,

ahol Y‾1\bar{Y}_1 a treatment kar conversion_7d átlaga, Y‾0\bar{Y}_0 a kontroll karé. Mindkettő arány, dimenzió nélküli, 0 és 1 közötti érték. A standard error független karok esetén:

SE(Δ̂)=p0(1−p0)n0+p1(1−p1)n1, SE(\hat{\Delta}) = \sqrt{\frac{p_0(1-p_0)}{n_0} + \frac{p_1(1-p_1)}{n_1}},

ahol p0p_0 és p1p_1 a két kar konverziós aránya, n0n_0 és n1n_1 a karméretek.

A kísérleti erő (power) annak a valószínűsége, hogy a teszt elutasítja a nullhipotézist, ha a valódi hatás Δ\Delta. Kétoldali 5%-os szinten, 80%-os erőhöz tartozó minimálisan detektálható hatás közelítőleg:

δMDE≈(z1−α/2+z1−β)2p(1−p)n/2, \delta_{MDE} \approx (z_{1-\alpha/2} + z_{1-\beta}) \sqrt{\frac{2p(1-p)}{n/2}},

ahol z1−α/2=1.96z_{1-\alpha/2} = 1.96, z1−β=0.84z_{1-\beta} = 0.84, pp a tervezett kontrollarány, nn a teljes minta. A mi 300 ügyfelünkre p=0.25p = 0.25 mellett a gyökös tag 2⋅0.25⋅0.75/150≈0.05\sqrt{2 \cdot 0.25 \cdot 0.75 / 150} \approx 0.05, így δMDE≈0.14\delta_{MDE} \approx 0.14. A 0.10-es hatás kimutatásához tehát a kísérlet alul-powered; a protokoll ezt explicit korlátként tartalmazza.

18.2.4 Teljes minimális munkapélda: a protokoll és az ellenőrzés

A protokoll döntési lapja:

Elem Rögzített érték
Randomizálási egység customer_id
Hozzárendelés 1:1 control/treatment, 2025-07-01
Primer endpoint conversion_7d
ITT nevező mind a 300 randomizált ügyfél
Megfigyelési ablak fix 7 nap, observed_until = 2025-07-08T00:00:00+00:00
Döntési küszöb bootstrap 95% CI alsó határ > 0.02 → rollout
Guardrail revenue_7d_minor treatment átlag >= 0.95 * kontroll átlag

A döntési szabály tehát: ha a 95%-os percentilis bootstrap CI alsó határa nagyobb, mint 0.02, javasoljuk a bevezetést; ha a CI felső határa kisebb, mint 0.02, elutasítjuk; különben collect_more. A guardrail sérelme esetén a döntés mindig block.

Az alábbi kód szemléltető: a kimeneti számok elméletileg vártak, nem mért futási eredmények. Futtatási környezet: projektgyökér, ahol a lab/ mappa található.

# s070_protocol.py — szemléltető, nem futtatott kód
import numpy as np
import pandas as pd

ASSIGN_PATH = "lab/assignments.csv"
OUTCOME_PATH = "lab/outcomes.csv"
EXPECTED_OBSERVED_UNTIL = "2025-07-08T00:00:00+00:00"
BOOT_SEED = 20260701

def load_and_validate():
    assign = pd.read_csv(ASSIGN_PATH)
    outcomes = pd.read_csv(OUTCOME_PATH)
    df = assign.merge(outcomes, on="customer_id", validate="one_to_one")
    errors = []
    if len(df) != 300:
        errors.append(f"Várt 300 sor, kapott {len(df)}")
    if not df["customer_id"].is_unique:
        errors.append("customer_id nem egyedi")
    if set(df["variant"]) != {"control", "treatment"}:
        errors.append("variant értékkészlet hibás")
    if df["conversion_7d"].isna().any():
        errors.append("conversion_7d hiányzik")
    if (df["observed_until"] != EXPECTED_OBSERVED_UNTIL).any():
        errors.append("observed_until eltér a fix ablaktól")
    if (df["revenue_7d_minor"] < 0).any():
        errors.append("negatív revenue_7d_minor")
    return df, errors

def bootstrap_diff_ci(df, n_boot=2000, seed=BOOT_SEED):
    rng = np.random.default_rng(seed)
    control = df.loc[df["variant"] == "control", "conversion_7d"].to_numpy()
    treatment = df.loc[df["variant"] == "treatment", "conversion_7d"].to_numpy()
    diffs = np.empty(n_boot)
    for i in range(n_boot):
        c = rng.choice(control, size=len(control), replace=True)
        t = rng.choice(treatment, size=len(treatment), replace=True)
        diffs[i] = t.mean() - c.mean()
    return np.quantile(diffs, [0.025, 0.975])

def evaluate_protocol(df, n_boot=2000, seed=BOOT_SEED):
    control = df.loc[df["variant"] == "control", "conversion_7d"]
    treatment = df.loc[df["variant"] == "treatment", "conversion_7d"]
    delta = treatment.mean() - control.mean()
    se = np.sqrt(
        treatment.mean() * (1 - treatment.mean()) / len(treatment)
        + control.mean() * (1 - control.mean()) / len(control)
    )
    ci_lo, ci_hi = bootstrap_diff_ci(df, n_boot, seed)
    control_rev = df.loc[df["variant"] == "control", "revenue_7d_minor"].mean()
    treatment_rev = df.loc[df["variant"] == "treatment", "revenue_7d_minor"].mean()
    guardrail_ok = treatment_rev >= 0.95 * control_rev
    if not guardrail_ok:
        decision = "block"
    elif ci_lo > 0.02:
        decision = "rollout"
    elif ci_hi < 0.02:
        decision = "reject"
    else:
        decision = "collect_more"
    return {
        "delta": delta,
        "se": se,
        "ci": (ci_lo, ci_hi),
        "guardrail_ok": guardrail_ok,
        "decision": decision,
    }

df, errors = load_and_validate()
print(errors)          # elvártan üres lista
if not errors:
    result = evaluate_protocol(df)
    print(result)

A kód ellenőrzi a protokoll feltételeit, majd kiszámítja az ITT becslést. A bootstrap csoporton belüli: minden ismétlésben külön-külön mintavételezünk a kontroll és a treatment karból, mert a karok méretét a protokoll rögzíti, és a randomizálási egység az ügyfél.

18.2.5 Eredmény értelmezése

A merge után a DataFrame 300 soros; a validate="one_to_one" garantálja, hogy minden ügyfélhez pontosan egy kimenet tartozik. A conversion_7d dtype várhatóan integer (0/1), a variant object típusú kategória.

Kézi kontroll: a normal approximationból számolt se és a bootstrap CI közelítőleg összhangban van; 300 mintánál a percentilis bootstrap CI nem feltétlenül szimmetrikus, de a két módszer ugyanazt a nagyságrendet adja. A becsült delta a generáló folyamat miatt várhatóan 0.10 körül mozog, de a véletlen miatt ettől több százalékponttal is eltérhet. A döntési szabály ezért nem a pontbecslést, hanem a CI és a 0.02-es küszöb viszonyát nézi.

Ha a guardrail megsérül, a döntés block akkor is, ha a primer hatás pozitív. Ez a protokoll előre rögzített része; az adat megtekintése után nem módosítható.

Kísérleti minőségbiztosítás: külön kell választani a randomizált kiosztást, a tényleges expozíciót és a kimenet elérhetőségét. ITT-elemzésnél a randomizált nevezőt kell alapul venni; a hiányzó kimenetek nem kezelhetők automatikusan nullásként.

18.2.6 Feladatok

  1. Értelmezés. Miért nem lehet a kísérlet elemzési egysége a rendelési tétel vagy a rendelés, ha a lab/order_items.csv-t összekapcsoljuk az ügyfelekkel? Magyarázd meg az ITT nevező és a függetlenség szempontjából.

  2. Implementálás. Írj validate_protocol(df) függvényt, amely a fenti ellenőrzéseken túl azt is vizsgálja, hogy a variant karonként 150-150 ügyfelet tartalmaz, és hogy minden assigned_at értéke 2025-07-01T00:00:00+00:00. A függvény hibák listájával térjen vissza; adj meg egy invariant, amellyel a kimenetet ellenőrzöd.

  3. Hibakeresés. Egy elemző csak azokat az ügyfeleket tartja meg, akiknél revenue_7d_minor > 0, mert szerinte csak ők „aktívak”, és a bevételi hatást csak rajtuk akarja mérni. Mi a hiba? Adj egy 10+10 ügyféles számpéldát, ahol az ITT bevételi hatás +48 cent, de a szűrt becslés +35 cent.

  4. Megváltozott feltétel. Tegyük fel, hogy a kezelés egy nyilvános weboldali kedvezmény, amelyet a kontroll ügyfelek is láthatnak, ha ugyanabban a régióban élnek. Melyik protokollfeltétel sérül? Javasolj alternatív protokollt, és nevezd meg annak korlátját.

  5. Transzfer. A primer endpoint revenue_7d_minor-ra változik. Hogyan módosul a standard error képlete és a döntési szabály? Milyen hatásméretet használnál?

18.2.7 Megoldások és indoklás

1. megoldás. A randomizálás ügyfélszintű; a rendelések és tételek egy ügyfélhez tartoznak, tehát nem független megfigyelések. Ha a tételeket külön sorként kezelnénk, a minta látszólag 4796 sorra nőne, de az azonos ügyfélhez tartozó sorok közös sorsolást örökölnek, így a standard error alulbecsült lenne, és a CI túl szűk. Az ITT nevező ezért mindig a randomizálási egységek száma: 300 ügyfél.

2. megoldás. Egy lehetséges függvény:

def validate_protocol(df):
    errors = []
    if df["variant"].value_counts().to_dict() != {"control": 150, "treatment": 150}:
        errors.append("A karok mérete nem 150-150")
    if (df["assigned_at"] != "2025-07-01T00:00:00+00:00").any():
        errors.append("assigned_at eltér a protokolltól")
    return errors

Invariant: ha a bemenet a tiszta laborfájl, a hibák listája üres. Ellenőrzés: a value_counts().to_dict() pontosan a két várt kulcsot adja; a dátum-összehasonlítás stringként is működik, mert az ISO formátum fix.

3. megoldás. A szűrés exposure utáni változón alapul: a revenue_7d_minor > 0 maga is a kezelés következménye lehet. Ha a kezelés növeli a vásárlási valószínűséget, a treatment karból több ügyfél kerül a szűrt halmazba, és a két csoport összetétele már nem véletlenszerű. Számpélda: 10 treatment ügyfél közül 6 vásárol, revenue-k 100, 110, 120, 130, 140, 150 cent; 4 nem vásárol (0). Treatment ITT átlag: 6⋅125/10=756 \cdot 125 / 10 = 75. Kontroll: 10-ből 3 vásárol, revenue-k 80, 90, 100 cent; ITT átlag: 3⋅90/10=273 \cdot 90 / 10 = 27. A valódi ITT hatás 75−27=4875 - 27 = 48 cent. Ha csak a pozitív revenue-jű sorokat nézzük: treatment átlag 125, kontroll átlag 90, különbség 35 cent. A szűrés eltünteti a konverziónövelő hatás egy részét.

4. megoldás. Az interference sérül: a kontroll ügyfelek kimenete nem a tiszta Y(0)Y(0), mert ők is látják a kedvezményt, ami a kontroll-konverziót is megemelheti, így az ATE alulbecsült. Alternatíva: régiószintű klaszter-randomizálás, ahol az egész régió kap vagy nem kap kezelést. Ennek korlátja: a Northstar adatban csak 3 régió van, ami túl kevés független klaszter a megbízható következtetéshez; nagyobb mintában viszont a klaszteren belüli korrelációt figyelembe vevő standard error kell. Egy másik alternatíva: a kezelést egyénileg elszigetelni, például emailben küldött egyedi kuponnal.

5. megoldás. Folytonos kimenetre a standard error:

SE(Δ̂)=s02n0+s12n1, SE(\hat{\Delta}) = \sqrt{\frac{s_0^2}{n_0} + \frac{s_1^2}{n_1}},

ahol s02s_0^2 és s12s_1^2 a karok mintabeli varianciája, centben kifejezve. A döntési szabály ugyanaz maradhat, de a 0.02-es küszöb helyett pénzben rögzített minimális gyakorlati hatás kell, például 20 cent. Hatásméretként Cohen-féle dd is használható: Δ̂/spooled\hat{\Delta} / s_{pooled}, ahol spooleds_{pooled} a két kar egyesített szórása.

18.2.8 Gyakori tévedés ellenpéldával

A leggyakoribb hiba az exposure utáni szelekció: a randomizálás után csak azokat az ügyfeleket elemezni, akiknél a kezelés „működött”, vagy akiknél van megfigyelt aktivitás. A randomizálás az eredeti karok összehasonlíthatóságát adja; ha utána szűrünk, a szűrés függhet a kezeléstől, és az Oksági cél és megfigyelési torzítás alfejezetben megismert collider torzítás lép fel. A fenti 3. feladat számpéldája mutatja, hogy a valódi +48 centes ITT hatásból a szűrt becslés +35 cent lesz. Az ITT elv pontosan ezt védi ki: minden randomizált ügyfél a saját karjában marad, akkor is, ha nem kapta meg a kezelést vagy nem konvertált.

Önellenőrzés: tudd megnevezni a randomizálási egységet a laborban; tudd, miért nem a rendelési tétel; tudd, mi a különbség a hozzárendelés és a tényleges exposure között; tudd, hogy a guardrail szabályát mikor kell rögzíteni; tudd, melyik feltevés sérül, ha a kontroll ügyfelek is látják a kezelést.

18.2.9 Rövid összefoglaló

A randomizált kísérlet protokollja a randomizálási egységet, a hozzárendelést, a primer endpointot, az időablakot és a döntési szabályt előre rögzíti. Az ITT elv szerint minden randomizált ügyfél a saját karjában marad; a tényleges exposure szerinti szűrés újra zavaró hatást vihet be. A guardrail a bevezetést védi, az attrition és az interference pedig érvényességi feltétel. A Northstar 300 ügyfele 1:1 arányban alkalmas a hatás becslésére, de 80%-os erővel csak kb. 14 százalékpontos hatást tudna kimutatni, ezért a döntés CI-alapú és a 0.02-es küszöbhöz kötött. Következő lépésként Megfigyeléses becslés és érzékenység alfejezetben megfigyeléses becslést és érzékenységvizsgálatot nézünk, amikor randomizálás nem áll rendelkezésre.