40.4 X. ellenőrző pont: önálló data scientist munkamenet

Záró audit: az elfogadási jegyzőkönyv mutassa be a split és a rendelkezésre állási idő ellenőrzését, az adatminőségi hibapályát, a környezetből újrafuttatást, az előzetes statisztikai tervhez való megfelelést és az eredménykorlátok közlését. A sikeres futtatás önmagában nem helyettesíti a módszertani auditot.

40.4.1 Cél és konkrét helyzet

Az X. ellenőrző pont azt méri, hogy a könyv tíz állomásán tanult munkamenetet egyetlen, új feladaton is önállóan végig tudod vinni: a kérdés megfogalmazásától a döntési jelentésig. A korábbi Tudományos jelentés és reprodukálható bemutató tudományos közlésre, Átadás és új környezetes ismétlés átadásra, IX. ellenőrző pont: működő rendszer és bizonyítékai működő rendszerre, VII. ellenőrző pont: szakosodási döntés kontrollal módszerválasztásra készített fel; itt ezeknek együtt kell működniük.

A példafeladat egy teljesen új, fiktív szintetikus kísérlet: a MicroGrove nevű képzeletbeli bolt 800 véletlenszerűen kiválasztott ügyfele közül 400-400 kap kontrol-, illetve treatment-állapotot. A treatment egy e-mailes kupon, a kontrol egy semleges hírlevél. A döntési kérdés: növeli-e a kupon a rendeléskor megfigyelhető 7 napos konverziót (conversion_7d)? Az adatok nem valós ügyfelektől származnak; a generátor kódja a lentiekben található.

Bemenetek, megfigyelési egység és eredmény. A labor a lab/experiments/microgrove/ könyvtárban él:

Fájl Szemcsézettség Tartalom
assignments.csv egy randomizált ügyfél customer_id, variant, assigned_at
outcomes.csv ugyanaz az ügyfél conversion_7d, revenue_7d_minor, currency, observed_until
manifest.json fájlszintű seed, elemszám, CSV SHA-256 lenyomatok
result.json kísérleti szintű becslés, CI, p-érték

Minden randomizált ügyfél teljes 7 napos ablakon megfigyelt: observed_until = 2025-11-10T00:00:00+00:00, cenzúra nincs. A primer végpont a conversion_7d, értéke 0 vagy 1. Az intention-to-treat (ITT) nevező mind a 800 randomizált ügyfél, akkor is, ha valaki nem nyitja meg az e-mailt. A revenue_7d_minor egész EUR-cent; pénznemkonverzió nincs.

Induló becslés a kód futtatása előtt. Tegyük fel, hogy korábbi kampányok alapján a kontrolkonverzió pC≈0.20p_C \approx 0.20, a kezelés hatására pedig pT≈0.28p_T \approx 0.28 várható. nT=nC=400n_T=n_C=400 mellett a Wald-féle standard error tervezett értéke

SE=pT(1−pT)nT+pC(1−pC)nC=0.28⋅0.72400+0.20⋅0.80400≈0.030. \mathrm{SE} = \sqrt{\frac{p_T(1-p_T)}{n_T}+\frac{p_C(1-p_C)}{n_C}} = \sqrt{\frac{0.28\cdot0.72}{400}+\frac{0.20\cdot0.80}{400}} \approx 0.030 .

Így a 95%-os konfidenciaintervallum várható szélessége 2⋅1.96⋅0.030≈0.1182\cdot1.96\cdot0.030\approx0.118, a 0.080.08 pontnyi hatás mellett a tesztstatisztika várhatóan z≈2.66z\approx2.66 körül lesz. Ez a kézi kontroll lesz az összehasonlítási alapunk; a tényleges minta a véletlen miatt ettől eltér.

40.4.2 Fogalmi magyarázat: hét munkafázis egyetlen kísérleten

Az ellenőrző pont nem új módszert vezet be, hanem a tanult fogalmakat rendezi hét fázisba. Mindegyik fázisnak van egy konkrét, ellenőrizhető kimenete.

  1. Problem framing — kérdés pontosítása. A kérdés nem „jó-e a kupon?”, hanem: „a treatment-felajánlás τ\tau átlagos hatása a 7 napos konverzióra mekkora, és mekkora a becslés bizonytalansága?” A döntési küszöböt előre rögzítjük: a bolt akkor vezeti be a kampányt, ha az abszolút hatás legalább 5 százalékpont (pp) konverziónövekedés. A küszöb nélkül a p-érték önmagában nem üzleti döntés.
  2. Stats — becslés és bizonytalanság. A két csoport konverzióaránya p̂T\hat p_T és p̂C\hat p_C; a pontbecslés

τ̂ITT=p̂T−p̂C, \hat\tau_{\mathrm{ITT}} = \hat p_T - \hat p_C ,

ahol pT,pC∈[0,1]p_T,p_C \in [0,1] valószínűségek, nT,nCn_T,n_C csoportméretek, τ̂\hat\tau aránykülönbség, szintén [−1,1][-1,1]-ben. A Wald-standard error a fenti képlet, a 95%-os intervallum

τ̂±z0.975SE, \hat\tau \pm z_{0.975}\,\mathrm{SE},

ahol z0.975≈1.96z_{0.975}\approx1.96. A p-érték a nullmodell szerinti feltételes szélsőség: ha τ=0\tau=0, mekkora valószínűséggel kapnánk legalább ekkora abszolút eltérést? A p-érték nem annak a valószínűsége, hogy nincs hatás. 3. Modeling — a statisztikai modell. Itt nem gépi tanulási modell kell, hanem egy kétmintás Bernoulli-modell: Yg,i∼Bernoulli(pg)Y_{g,i}\sim\mathrm{Bernoulli}(p_g), ahol g∈{T,C}g\in\{T,C\} csoport, ii ügyfél. A modell a csoportátlagok különbségét becsüli. A bootstrap ezt a modellt lazítja: nem követeli meg a normális közelítést, de megtartja a független ügyfélszintű mintavételt. 4. Code — reprodukálható kód. A seed rögzített, a fájlok hash-manifesttel védettek, a kód a projektgyökérből uv run paranccsal futtatható. Az analysis.py nem kér hálózatot, és a bootstrap random state rögzítésével determinisztikus kimenetet ad. 5. Runtime — végrehajtási közeg. A feladat CPU-s, 800 sorral és 9999 bootstrap-újramintázással kicsi. A tényleges futási időt a tanuló gépén kell mérni; ebben a szövegben nem állítunk mért másodpercet. 6. Communication — döntési jelentés. A kimenet nem csak egy p-érték: pontbecslés, mindkét CI, hatásméret, döntési küszöb és a korlátok is bekerülnek a jelentésbe. 7. Learning plan — továbbfejlődési célok. Az ellenőrző pont végén a portfólió alapján három konkrét fejlődési célt kell kijelölni. A céloknak mérhetőnek kell lenniük: például „a következő héten egy saját szintetikus A/B-feladatot a Bootstrap a megfelelő mintavételi egységgel bootstrap-egységének helyes megnevezésével oldok meg”.

A hét fázisból a statisztikai fázis hordozza a legnagyobb értelmezési kockázatot. A konfidenciaintervallum a paraméter bizonytalanságát fejezi ki; az új ügyfél kimenetére vonatkozó predikciós intervallum más, ahogy a credible interval értelmezése is. A MicroGrove-elemzésben végig paraméter-intervallumról beszélünk.

40.4.3 Teljes minimális munkapélda

Hozzuk létre a laborfájlokat. Első a szintetikus generátor; a seed 20270213 szándékosan más, mint a Northstar 20261020 seedje. A generátorban a lognormális kosár paraméterezésekor a E[X]=exp(μ+σ2/2)E[X]=\exp(\mu+\sigma^2/2) összefüggést használjuk, ezért μ=log(m)−σ2/2\mu=\log(m)-\sigma^2/2, ahol mm a megcélzott kosárátlag centben.

lab/experiments/microgrove/generate_microgrove.py:

import hashlib
import json
from pathlib import Path

import numpy as np
import pandas as pd

BASE = Path(__file__).resolve().parent
SEED = 20270213
N = 800
ASSIGNED_AT = "2025-11-03T09:00:00+00:00"
OBSERVED_UNTIL = "2025-11-10T00:00:00+00:00"

rng = np.random.default_rng(SEED)
ids = [f"mg-{i:04d}" for i in range(N)]
treatment = np.zeros(N, dtype=int)
treatment[N // 2:] = 1
rng.shuffle(treatment)

p_control = 0.20
p_treatment = 0.28
p = np.where(treatment == 1, p_treatment, p_control)
conversion = rng.binomial(1, p).astype(int)

target_basket = np.where(treatment == 1, 7200, 6400)
sigma = 0.45
mu = np.log(target_basket) - 0.5 * sigma**2
basket = np.round(rng.lognormal(mean=mu, sigma=sigma)).astype(int)
basket = np.where(conversion == 1, basket, 0)

assignments = pd.DataFrame(
    {
        "customer_id": ids,
        "variant": np.where(treatment == 1, "treatment", "control"),
        "assigned_at": ASSIGNED_AT,
    }
)
outcomes = pd.DataFrame(
    {
        "customer_id": ids,
        "conversion_7d": conversion,
        "revenue_7d_minor": basket,
        "currency": "EUR",
        "observed_until": OBSERVED_UNTIL,
    }
)
assignments.to_csv(BASE / "assignments.csv", index=False)
outcomes.to_csv(BASE / "outcomes.csv", index=False)

def sha256(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()

manifest = {
    "seed": SEED,
    "n": N,
    "files": {
        "assignments.csv": sha256(BASE / "assignments.csv"),
        "outcomes.csv": sha256(BASE / "outcomes.csv"),
    },
}
(BASE / "manifest.json").write_text(json.dumps(manifest, indent=2))
print("microgrove data generated")

Második az elemzés. A verify_manifest a CSV-k integritását ellenőrzi, mielőtt bármilyen becslést számolnánk. A bootstrap az ügyfél szintjén újramintáz, nem a sorok tetszőleges keverésével; ez a független mintavételi egység.

lab/experiments/microgrove/analysis.py:

import hashlib
import json
from pathlib import Path

import numpy as np
import pandas as pd
from scipy import stats

BASE = Path(__file__).resolve().parent

def sha256(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()

def verify_manifest(base: Path) -> None:
    manifest = json.loads((base / "manifest.json").read_text())
    for name, expected in manifest["files"].items():
        actual = sha256(base / name)
        if actual != expected:
            raise RuntimeError(f"hash mismatch: {name}")
    print("manifest ok")

def diff_mean(t: np.ndarray, c: np.ndarray) -> float:
    return float(np.mean(t) - np.mean(c))

def main() -> None:
    verify_manifest(BASE)

    assignments = pd.read_csv(
        BASE / "assignments.csv",
        dtype={"customer_id": "string", "variant": "string"},
    )
    outcomes = pd.read_csv(
        BASE / "outcomes.csv",
        dtype={"customer_id": "string", "conversion_7d": "int64",
               "revenue_7d_minor": "int64"},
    )
    df = assignments.merge(
        outcomes, on="customer_id", validate="one_to_one"
    )
    if len(df) != len(assignments):
        raise ValueError("missing or duplicate outcome row")
    if not df["conversion_7d"].isin([0, 1]).all():
        raise ValueError("conversion_7d must be 0 or 1")

    y = df["conversion_7d"].to_numpy(dtype=float)
    treat = (df["variant"].to_numpy() == "treatment")
    y_t, y_c = y[treat], y[~treat]
    n_t, n_c = y_t.size, y_c.size
    p_t, p_c = y_t.mean(), y_c.mean()
    tau = p_t - p_c
    se = np.sqrt(p_t * (1 - p_t) / n_t + p_c * (1 - p_c) / n_c)
    ci_wald = tau + np.array([-1.0, 1.0]) * stats.norm.ppf(0.975) * se
    z = tau / se
    p_value = 2 * (1 - stats.norm.cdf(abs(z)))

    boot = stats.bootstrap(
        (y_t, y_c),
        statistic=diff_mean,
        paired=False,
        n_resamples=9999,
        random_state=20270213,
        method="BCa",
        confidence_level=0.95,
    )

    result = {
        "n_treatment": int(n_t),
        "n_control": int(n_c),
        "p_treatment": float(p_t),
        "p_control": float(p_c),
        "tau_hat": float(tau),
        "se": float(se),
        "ci_95_wald": [float(x) for x in ci_wald],
        "z": float(z),
        "p_value_two_sided": float(p_value),
        "ci_95_bca": [float(x) for x in boot.confidence_interval],
    }
    (BASE / "result.json").write_text(json.dumps(result, indent=2))
    print(json.dumps(result, indent=2))

if __name__ == "__main__":
    main()

Futtatás a projektgyökérből, Linux/macOS terminálon; Windows PowerShellben ugyanez a két sor használható:

uv run python lab/experiments/microgrove/generate_microgrove.py
uv run python lab/experiments/microgrove/analysis.py

A Python 3.12, NumPy 2.x, pandas 2.2.x és SciPy 1.15 körüli környezet a cél; a pontos feloldást a saját uv lock rögzíti. A kód nem igényel hálózatot, GPU-t vagy külső szolgáltatást.

40.4.4 Eredmény értelmezése

Az alábbi összefoglaló szemléltető, az elméleti paraméterekből levezetett várt érték, nem ennek a könyvgenerátornak a futtatási jegyzéke. A tanuló gépén a seed miatt kis mértékben más számok jelennek meg:

manifest ok
n_treatment=400, n_control=400
p_treatment≈0.28, p_control≈0.20
tau_hat≈0.08
se≈0.030
wald CI≈[0.021, 0.139]
bca CI hasonló, kis aszimmetriával
z≈2.66, p≈0.008

Az ellenőrző invariantok: a beolvasott df shape (800, 7); a customer_id string dtype, a variant string, a conversion_7d és revenue_7d_minor egész; a merge validate="one_to_one" sikeres, tehát nincs hiányzó vagy duplikált kimenet. A tau_hat aránykülönbség, pp-re váltva 100-szoros. A p-érték helyes feltételes mondata: ha a valódi hatás nulla lenne, a megfigyelt vagy szélsőségesebb különbség valószínűsége körülbelül 0.008 a nullmodell szerint.

A döntési értelmezés a küszöbbel együtt történik. Az 5 pp bevezetési küszöb mellett a szemléltető CI alsó határa körülbelül 2.1 pp, tehát a minta nem erősíti meg, hogy a hatás biztosan eléri a küszöböt; a pontbecslés igen, de a bizonytalanság nagy. Ha a küszöb 2 pp lenne, akkor a 2.1 pp alsó határ éppen elegendő. Ez a különbségtétel a tudományos jelentés magja: a szignifikancia a nullával, a döntés a küszöbbel hasonlít össze.

40.4.5 Feladatok

  1. Értelmezés. A scipy.stats.bootstrap hívásában a data=(y_t, y_c) tömbök ügyfélszintű 0/1 értékeket tartalmaznak. Magyarázd el, miért helyes az ügyfél a resampling egység, és miért lenne hibás, ha a bootstrapet például a tranzakciós revenue_7d_minor sorokból képzett hosszabb tömbön futtatnánk olyan adatbázisból, ahol egy ügyfélnek több sora is lehet.
  2. Megvalósítás. Bővítsd az analysis.py kódot a revenue_7d_minor ITT-elemzésével: csoportátlagok különbsége, standard error, bootstrap percentilis CI. A kimeneti JSON-ban revenue_tau_hat, revenue_ci_95_bca kulcsok alatt szerepeljen az eredmény. Ellenőrzésként írd ki a nem konvertálók arányát csoportonként.
  3. Hibakeresés. Egy másik jegyzetfüzetben valaki ezt írta a p-érték kiszámítására: p_value = 2 * (1 - stats.norm.cdf(z)), ahol z az előjeles tesztstatisztika. Adj olyan szituációt, amikor ez hibás eredményt ad, és magyarázd meg a javítást.
  4. Megváltoztatott feltétel. A valódi kampány során a marketingrendszer a treatment csoport 10%-ának nem küldte el az e-mailt. A randomizálás továbbra is érvényes, de a felajánlott kezelés csak 90%-ban jutott el. Hogyan változik az ITT-becslés várható értéke homogén kezelési hatás feltételezése mellett? Milyen érzékenységvizsgálatot javasolnál a jelentésbe?

40.4.6 Megoldások és indoklás

1. feladat. A randomizálás egysége az ügyfél: a treatment sorsolás ügyfélenként független, ezért a becslés bizonytalanságát az ügyfélszintű véletlen ingadozás határozza meg. Ha egy ügyfél több tranzakciós sorral szerepelne, és a bootstrap sorokból húzna, akkor egy sokvásárlós ügyfél többszörösen jelenne meg, a függetlenség sérülne, a standard error pedig jellemzően alulbecsült lenne. A MicroGrove outcomes.csv egy ügyfélhez pontosan egy sort rendel, ezért a hiba itt nem lép fel; a feladat arra figyelmeztet, hogy a resampling egységet a kísérleti tervből kell levezetni, nem a fájl sorszámából.

2. feladat. Az outcomes.csv revenue_7d_minor értékei erősen ferde eloszlást követnek: a nem konvertálók értéke 0, a konvertálók kosara lognormális. A normálközelítés itt gyenge lehet, ezért a bootstrap indokolt. Egy lehetséges bővítés:

rev = df["revenue_7d_minor"].to_numpy(dtype=float)
rev_t, rev_c = rev[treat], rev[~treat]
rev_tau = rev_t.mean() - rev_c.mean()
rev_boot = stats.bootstrap(
    (rev_t, rev_c),
    statistic=diff_mean,
    paired=False,
    n_resamples=9999,
    random_state=20270213,
    method="BCa",
)
result["revenue_tau_hat"] = float(rev_tau)
result["revenue_ci_95_bca"] = [
    float(x) for x in rev_boot.confidence_interval
]

Ellenőrző invariant: a groupby-átlagok újraszámolva megegyeznek a diff_mean kimenetével 1 centen belül; a nem konvertálók aránya a mintabeli 1 - p_t és 1 - p_c, ami a szemléltető paraméterekkel 0.72 és 0.80 körül várható.

3. feladat. Ha a mintából számított hatás negatív, például z = -2.0, akkor a hibás kifejezés 2 * (1 - cdf(-2.0)) = 2 * (1 - 0.0228) = 1.9544-et adna, ami p-értékként lehetetlen. A kétoldali p-értékhez az abszolút eltérést kell nézni: 2 * (1 - stats.norm.cdf(abs(z))). A javítás után z = -2.0 esetén a p-érték körülbelül 0.0455. Az ellenőrzés módja: mesterségesen előjelet váltva a p-értéknek változatlannak kell maradnia.

4. feladat. Ha a felajánlott kezelés csak 90%-ban jut el, és a hatás homogén, akkor az ITT-becslés várható értéke τITT≈0.9⋅τLATE\tau_{\mathrm{ITT}} \approx 0.9 \cdot \tau_{\mathrm{LATE}}, ahol τLATE\tau_{\mathrm{LATE}} a ténylegesen kezeltek átlagos hatása. A 0.08 pontos tervezett hatás így körülbelül 0.072 pontra hígul. A protokoll szerinti ITT továbbra is a randomizálásból következő torzítatlan becslés marad, de a döntési jelentésben érzékenységvizsgálatot kell közölni: adjunk felső becslést a ténylegesen kezeltek hatására a felajánlás/elérés aránnyal osztva, és jelezzük, hogy ez erős homogenitási feltétel mellett érvényes. A csoportméret növelése nem pótolja ezt az elemzést.

40.4.7 Gyakori tévedés és ellenpélda

A leggyakoribb tévedés ezen az ellenőrző ponton az, hogy a tanuló a könyv elvégzését vagy egy sikeres demót munkaköri minősítésként kezeli. A tanúsítvány csak a tanulási utat igazolja; a tényleges teljesítményt az új feladaton mutatott önálló következtetés bizonyítja. Két jelölt közül az egyik hibátlan portfóliót mutat, de a MicroGrove p-értékét így közli: „3% az esélye, hogy nincs hatás”; a másik nem végzett el minden opcionális modult, de a p-értéket feltételes állításként fogalmazza, és a CI alsó határát a döntési küszöbhöz méri. Az utóbbi teljesítménye az erősebb.

Egy másik csapda a korábbi Northstar-kódból való kritikátlan másolás: a prediktív projekt időbeli splitje nem alkalmazható egy randomizált kísérletre. A kísérletben nincs train/test split; a randomizálás és a teljes megfigyelés adja az érvényességet. Visszautalva Tanítás, validáció és érintetlen teszt alfejezetre: a split a predikciós szivárgást, a randomizálás a confoundingot kezeli; a kettő nem felcserélhető eszköz.

40.4.8 Önellenőrzés

A leadás előtt ellenőrizd ezt a listát:

40.4.9 Rövid összefoglaló

Az X. ellenőrző pont azt igazolja, hogy egy új szintetikus kísérleten a kérdéstől a döntési jelentésig önállóan végig tudod vinni a data scientist munkamenetet. A MicroGrove-példa ügyfélszintű randomizálást, teljes megfigyelésű 7 napos konverziót, ITT-becslést, Wald- és bootstrap konfidenciaintervallumot, p-értéket, manifest-hash ellenőrzést, döntési küszöböt és érzékenységvizsgálatot kapcsolt egybe. A teljesítményt nem a könyv elvégzése, hanem az új feladaton mutatott korrekt következtetés bizonyítja. Következő alkalmazásként válassz a saját portfóliódból három konkrét fejlődési célt, és egy újabb, saját tervezésű szintetikus esettanulmányon mérd meg mindegyiket.

40.4.10 Felhasznált és ajánlott módszertani források

A felsorolt tételek válogatott, visszakereshető kiinduló források, nem a könyv minden állításának tételes hivatkozásai. A szoftverfüggvények pontos viselkedését mindig a példaprojektben rögzített verzióval együtt kell ellenőrizni; az itt megadott dokumentációk „stable” ága idővel változhat.

[ISLR] James et al.: An Introduction to Statistical Learning (2021), https://www.statlearning.com/ (hozzáférés: 2026-10-21).

[Python] Python Packaging User Guide, https://packaging.python.org/ (hozzáférés: 2026-10-21).

[Pandas] pandas User Guide, https://pandas.pydata.org/docs/user_guide/ (hozzáférés: 2026-10-21).

[DuckDB] DuckDB SQL Documentation, https://duckdb.org/docs/stable/sql/introduction (hozzáférés: 2026-10-21).

[NumPy] NumPy Linear Algebra, https://numpy.org/doc/stable/reference/routines.linalg.html (hozzáférés: 2026-10-21).

[SciPy] scipy.stats Reference, https://docs.scipy.org/doc/scipy/reference/stats.html (hozzáférés: 2026-10-21).

[ASA] Wasserstein & Lazar (2016): The ASA Statement on p-Values, DOI: https://doi.org/10.1080/00031305.2016.1154108.

[Hernán] Hernán & Robins: Causal Inference: What If, https://www.hsph.harvard.edu/miguel-hernan/causal-inference-book/ (hozzáférés: 2026-10-21).

[PyMC] PyMC Documentation, https://www.pymc.io/projects/docs/en/stable/ (hozzáférés: 2026-10-21).

[Sklearn] scikit-learn User Guide, https://scikit-learn.org/stable/user_guide.html (hozzáférés: 2026-10-21).

[SklearnHGB] scikit-learn: Gradient Boosting and Categorical Features, https://scikit-learn.org/stable/modules/ensemble.html (hozzáférés: 2026-10-21).

[SklearnMetrics] scikit-learn Model Evaluation, https://scikit-learn.org/stable/modules/model_evaluation.html (hozzáférés: 2026-10-21).

[SklearnCalibration] scikit-learn Calibration, https://scikit-learn.org/stable/modules/calibration.html (hozzáférés: 2026-10-21).

[PyTorch] PyTorch Autograd Tutorial, https://docs.pytorch.org/tutorials/beginner/basics/autogradqs_tutorial.html (hozzáférés: 2026-10-21).

[FastAPI] FastAPI Documentation, https://fastapi.tiangolo.com/ (hozzáférés: 2026-10-21).

[R] R Manuals, https://cran.r-project.org/manuals.html (hozzáférés: 2026-10-21).