1.4 Diagnózis és első teljes munkamenet

1.4.1 Diagnózis: programozási, adatértelmezési és statisztikai hiány egy helyi munkamenetben

A data scientist munkája ritkán azzal kezdődik, hogy “futtassunk egy modellt”. Gyakran azzal kezdődik, hogy egy látszólag egyszerű ellenőrzés elromlik, vagy egy számról kiderül, hogy mást jelent, mint amit a neve sugall. Ebben az alfejezetben a Northstar labor segítségével egy teljes, kis diagnosztikai munkamenetet építünk fel. A cél nem egy bonyolult modell, hanem az, hogy a három leggyakoribb hibacsaládot — programozási hibát, adatértelmezési hibát és statisztikai túlkövetkeztetést — ugyanabban a feladatban külön tudjuk választani.

Konkrét helyzet. A labor a könyv projektgyökerének lab/ mappájában van. A bemenet a generate_data.py által előállított három tiszta CSV: data/customers.csv (300 ügyfél), data/orders.csv (2400 rendelés) és data/order_items.csv (4796 rendelési tétel). A megfigyelési egység a három fájlban rendre ügyfél, rendelés és rendelési tétel; ezt a szemcsézettséget tilos összemosni. A kimenet egy saját futási jegyzék, amelyben legalább a környezet, a három kontrollérték és a vizsgált alapmutató szerepel.

Előzetes becslés a megoldás előtt. Mielőtt futtatsz bármit, írd be a hipotézisnaplódba: szerinted a tiszta orders.csv-ben a rendelések hány százalékánál késik a kézbesítés a 7 napos vállalt határidőn túl? Adj egy alsó és egy felső határt, például “4% és 12% között”. Ezt a tippet később nem pontozzuk; az a szerepe, hogy rögzítse a várakozásodat, mielőtt a mérés befolyásolná a memóriádat. Ez a 1.3-ban bemutatott hipotézisnapló legegyszerűbb formája.

1.4.2 Darabszám, arány és százalékpont

A diagnosztikai feladat matematikája szándékosan kicsi. A darabszám (count) rekordok számát jelenti: hány sor van a fejlécen kívül. Az arány (proportion, rate) egy részsokaság darabszámának és a teljes nevezőnek a hányadosa. Ha NN a rendelések száma, NlateN_{\text{late}} pedig azok száma, amelyeknél a target_late oszlop értéke 1, akkor a késői kézbesítés aránya

plate=NlateN.p_{\text{late}} = \frac{N_{\text{late}}}{N}.

Itt platep_{\text{late}} dimenzió nélküli szám, értékkészlete [0,1][0,1]. Százalékban 100⋅plate100 \cdot p_{\text{late}} szerepel, mértékegysége %. A százalékpont (percentage point, pp) két, már százalékban megadott arány különbsége. Ha az egyik időszakban p0=0,060p_0=0{,}060 (azaz 6,0%6{,}0\%), egy másikban p1=0,080p_1=0{,}080 (8,0%8{,}0\%), akkor a különbség 2,02{,}0 százalékpont, ugyanakkor a relatív növekedés (p1−p0)/p0=0,333(p_1-p_0)/p_0=0{,}333, azaz 33,3%33{,}3\%. A kettő keverése az egyik legkönnyebben elkövethető hiba a jelentésekben.

A diagnózis másik tengelyét a három hibacsalád adja:

1.4.3 Teljes minimális munkamenet

A labor szándékosan zero-dependency: a generate_data.py és a quickcheck.py csak a Python standard library-t használja, tehát nem kell uv-környezetet létrehoznunk. A munkakönyvtár a lab/.

# platform: Linux/macOS vagy Windows PowerShell; munkakönyvtár: a projekt lab/ mappája
python --version
python generate_data.py
python quickcheck.py

A python --version kimenete a saját gépeden látszik; a tantervi alap Python 3.12, de a generátor ennél régebbi Python 3-ason is futtatható, ha a szintaxisa kompatibilis. A generate_data.py újraírja a szállított kis CSV-ket a rögzített seeddel. A quickcheck.py ezután a manifest.json alapján ellenőrzi a fájlokat. A három szerződött rekordszám a mellékelt adatszerződés szerint: customers.csv 300, orders.csv 2400, order_items.csv 4796. A pontos kimeneti formátum a labor verziójától függ; a döntő az, hogy e három értéknek a manifesttel egyeznie kell. Ebben az alfejezetben nem állítjuk, hogy most lefuttattuk a labort: a tényleges futás a te gépeden történik, és a rögzített kimenetet a te jegyzékedbe írod.

A quickcheck futtatása után a “kézi kontroll” azt jelenti, hogy legalább egyszer magunk is megszámoljuk a sorokat, nem csak elfogadjuk a kész kimenetet. A következő kis program a CSV-olvasót használja, így platformfüggetlenül, a fejléccel nem számolva adja vissza a rekordok számát:

from pathlib import Path
import csv

expected = {
    "customers.csv": 300,
    "orders.csv": 2400,
    "order_items.csv": 4796,
}

for name, want in expected.items():
    with (Path("data") / name).open(newline="", encoding="utf-8") as f:
        reader = csv.reader(f)
        header = next(reader)
        got = sum(1 for _ in reader)
    print(f"{name}: rows={got} columns={len(header)} expected={want} ok={got == want}")

A kimenet várható alakja mindhárom fájlra ok=True; a futási jegyzékben a generátor és a quickcheck pontos kimenetét is rögzítsd. A hash-ellenőrzésnél nincs tolerancia: a seed és a generátorverzió együtteséből bájtazonos állományok következnek, ezért itt az “elég közel” nem elfogadható. Ezt a szigorúságot a 1.3 futási jegyzékeléséből hozzuk át.

Ezután számítsuk ki az alapmutatót a rendelésszintű célváltozóra. A target_late a tiszta orders.csv-ben 0 vagy 1: 1, ha delivery_days > promised_days, azaz a kézbesítés késik a vállalt 7 naphoz képest. A tiszta adatállomány végén minden kézbesítés teljesen megfigyelt, ezért a nevező mind a 2400 rendelés lehet:

from pathlib import Path
import csv

with Path("data/orders.csv").open(newline="", encoding="utf-8") as f:
    orders = list(csv.DictReader(f))

n = len(orders)
unique_order_ids = {row["order_id"] for row in orders}
late = sum(1 for row in orders if row["target_late"] == "1")
ratio = late / n

print(f"orders: {n}")
print(f"unique order_id: {len(unique_order_ids)}")
print(f"late count: {late}")
print(f"late ratio: {ratio:.4f} ({100 * ratio:.2f}%)")

A fenti kódblokk kimenetét szándékosan nem tölthetjük ki a könyv írásakor azzal a látszattal, hogy most futott. A szerződött darabszámok viszont ismertek: orders: 2400 és unique order_id: 2400 várható. Ha ez a kettő nem teljesül, akkor a betöltött fájl nem az alap-adatszerződés szerinti, és a további számítás értelmezhetetlen. A late count és a late ratio értéke a te futtatásodból kerül a jegyzékbe; ezek determinisztikusak a rögzített seed mellett, de itt nem adjuk meg őket előre, hogy a becslés–futás–ellenőrzés ciklus valódi maradjon.

1.4.4 Mit jelent az eredmény?

A három kontrollérték az adathigiénia ellenőrzése: a fájlok a szerződött méretűek, tehát a későbbi elemzések ugyanarról a kiindulópontról indulnak. A unique order_id szám egyenlősége a rekordszámmal azt mutatja, hogy a tiszta orders.csv-ben nincs duplikált rendelés; ez a későbbi joinok előfeltétele. A késési arány ezzel szemben már tartalmi mutató: a platep_{\text{late}} pontbecslés egyetlen számmal foglalja össze a rendelésszintű késési gyakoriságot. Ennek a pontbecslésnek a bizonytalanságát itt még nem számszerűsítjük, de pontosan kimondjuk, hogy egyetlen szintetikus minta aránya nem azonos a generáló folyamat bizonytalan paraméterével, és végképp nem valós üzleti tény. A resampling-egység itt a rendelés; ha ügyfélszintű állítást tennénk, az ügyfél lenne a mintavételi egység, a tételszintű sor pedig soha nem adhat több mintát a rendelésekhez képest.

1.4.5 Feladatok

  1. Labdiagnózis és napló. Futtasd a zero-dependency labort, ellenőrizd kézzel a három kontrollértéket, és írj a hipotézisnaplóba öt olyan állítást, amelyet ez az adathalmaz nem támaszt alá. Mindegyik mellé egy mondatban indokold, miért nem.

  2. Késési arány implementálása. Írj olyan Python-programot, amely a data/orders.csv bemenetből kiírja a rendelésszámot, az egyedi order_id-k számát, a target_late == "1" sorok számát és a késési arányt százalékban, két tizedesjeggyel. A fejléc tartalmát ne számold rekordnak. Ellenőrizd, hogy az első két kiírt érték egyaránt 2400.

  3. Hibadiagnózis. Egy tanuló a következő kódot írta, és a késési darabszámra mindig 0 jött ki:

    with open("data/orders.csv", newline="", encoding="utf-8") as f:
        for row in csv.DictReader(f):
            if row["target_late"] == 1:
                late += 1

    Mi a hiba? Javítsd úgy, hogy a szöveges mezőt ne alakítsd át úgy, hogy a hiányzó érték rejtett hibává váljon.

  4. Megváltozott adat. A data/orders_raw.csv szándékosan nem a tiszta állomány: 2405 törzssort tartalmaz, benne öt extra duplikált sorral, két hiányzó distance_km értékkel, egy negatív basket_minor értékkel és egy nem létező customer_id-val. Adj három konkrét auditlépést, amely ezeket elkülöníti, és indokold, miért nem automatikusan törlünk minden gyanús sort.

1.4.6 Megoldások és indoklás

  1. Öt állítás, amelyet az adat nem támaszt alá — lehetséges megoldás.

  2. Megvalósítás. A fenti target_late-kód a megoldás. Ellenőrző invariantok: a fájl törzssorainak száma 2400; az egyedi order_id-k száma szintén 2400; 0≤Nlate≤24000 \le N_{\text{late}} \le 2400, ezért a kiszámolt arány [0,1][0,1]-beli. A pontos late count a futtatásból jön; a kimenetet a futási jegyzékbe kell rögzíteni, és a hash-ellenőrzéshez hasonlóan itt sincs tolerancia a véletlenül elírt fájlra.

  3. Hibadiagnózis. A csv.DictReader minden mezőt karakterláncként ad vissza, így row["target_late"] értéke "0" vagy "1", nem egész szám. A "1" == 1 kifejezés Pythonban False, ezért a számláló soha nem nő. Helyes összehasonlítás: row["target_late"] == "1". Az int(row["target_late"]) is működőképes, de csak akkor, ha előtte ellenőrizzük, hogy a mező pontosan "0" vagy "1"; különben a hiányzó vagy hibás érték ValueError-ként derül ki egy olyan ponton, ahol már nehezebb visszafejteni a forrássort.

  4. Auditlépések a nyers állományra.

    1. Csoportosíts order_id szerint, és listázd azokat, amelyekből egynél több sor van; ezek a duplikátumok auditrekordba kerülnek.
    2. Számold meg a hiányzó distance_km értékeket, és ellenőrizd a tiszta fájl sormennyiségét is; a hiányt ne pótold nullával csendben.
    3. Keresd a negatív basket_minor értékeket, és végezz anti-join ellenőrzést a customers.customer_id oszlopával az ismeretlen ügyfél megtalálásához.
      A sorok automatikus törlése azért hibás, mert a döntés üzleti/előállítási kontextus nélkül megsemmisíti azokat a nyomokat, amelyek a rendszerhiba okát mutatnák.

1.4.7 Gyakori tévedés és ellenpélda

A leggyakoribb tévedés ezen a ponton az, hogy a szintetikus adaton látott arányt valós üzleti bizonyítékként mondjuk fel. Ellenpélda: ha a tiszta orders.csv-ben a távoli rendelések körében magasabbnak látszik a késési arány, ebből még nem állítható, hogy a valódi bolt szállítási folyamata a távolsággal romlik. A labor egy mesterséges, paraméterezett szimuláció; a tanulság a munkamenetek szerkezetére vonatkozik, nem a valós vállalat teljesítményére. Ez visszautal a 1.3 hipotézisnaplójára is: a “megfigyeltem, hogy…” és a “az adat igazolja, hogy…” közötti lépést mindig külön kell rögzíteni, és a másodikhoz már következtetési szabály kell, nem csak egy CSV.

Önellenőrzés. Mielőtt továbblépnél, tudd megválaszolni: mi a megfigyelési egység a három fájlban külön-külön? Mi a nevező a késési arányban? Mi a különbség a 2 százalékpont és a 2 százalékos növekedés között? Meg tudod-e mondani minden elkészült állításodról, hogy adatból, feltételezésből vagy modellből következik-e? Ha valamelyikre nem, az a diagnózis következő tennivalója.

Diagnosztikai elfogadási kapu. A teljes munkamenet sikerét ne kizárólag a script nulla kilépési kódjához kösd. Külön ellenőrizd a bemeneti sémát, az egyedi rendelésazonosítókat, a hiányzások számát, a célképzés szabályát, a címke rendelkezésre állását és a kimeneti sorok számát. Az elfogadási jegyzőkönyv rögzítse az egyes állításokhoz tartozó megfigyelt értéket.

Szintetikus adaton mért késési arányból csak a generált állományra lehet közvetlenül következtetni. Valós működési ajánláshoz külön szükséges a mintavételi folyamat, az éles adatminőség és az időbeli általánosíthatóság igazolása.

1.4.8 Rövid összefoglaló

A diagnosztikai munkamenet három hibacsaládot választ szét: a programozási hiba a kód és a típusok szintjén jelentkezik; az adatértelmezési hiba a szemcsézettség és a mezők jelentésének félreértéséből ered; a statisztikai hiba pedig akkor, amikor egy pontbecslést bizonytalanság és mintavételi egység nélkül tényként kezelünk. A Northstar labor három kontrollértéke a tiszta adat szerződött méretét ellenőrzi, a késési arány pedig az első valódi alapmutató. A labort a tanuló futása és jegyzéke teszi megismételhetővé; a szintetikus adat tanulási minta, nem üzleti bizonyíték. A következő fejezetben ugyanezt a fegyelmet visszük át az operációs rendszer és a terminál szintjére: a fájlok, útvonalak és hibakimenetek kezelésére.