A Northstar-lab orders.csv táblája rendelésenként egy
sort tartalmaz: a megfigyelési egység itt egy rendelés,
az időbélyeg pedig az order_ts oszlop UTC-értéke. Ha a
kérdés az, hogyan alakul a rendelési volumen napról napra, az elemzési
egységet megváltoztatjuk: az új megfigyelési egység egy naptári
nap, a célváltozó pedig az aznapi rendelések darabszáma. A
feladat nem előrejelzés, hanem az idősor helyes időtengelyének és
hiányosságának előállítása, mert a későbbi modellezés Időhelyes feature és gördülő visszateszt ettől
függ.
A bemenet 2400 rendelés, a rendelések 2025 első 180 napjára esnek. Első becslésként napi 2400/180 ≈ 13,3 rendelést várunk, de a hét napjai között lehet eltérés. Az elvárt ellenőrzés:
DatetimeIndex;NaN értéket, dtype-ja int64.Gyakoriság (frequency). Egy idősor gyakorisága az
egymást követő megfigyelések közötti névleges időtávolság. A nyers
orders.csv szabálytalan gyakoriságú: két rendelés között
lehet 1 másodperc vagy 3 óra, nincs előre rögzített rács. Ha a
2025-01-01, 2025-01-02,
2025-01-04 napokon vannak megfigyelések, akkor az első két
lépés 1 nap, a harmadik 2 nap; ez nem nevezhető napi frekvenciájú
sorozatnak abban az értelemben, hogy a naptár minden napjához tartozik
érték.
A gyakorlatban a groupby("order_day") vagy a
resample("D") művelet napi frekvenciájúvá
(D) teszi a sorozatot: a naptár az a rács, amelyen az értékeket
értelmezzük. Az ennél durvább W heti, a finomabb
h órai rács szintén lehetséges, és a választás mindig a
döntési helyzettől függ.
Hézag (gap). A hézag a teljes időrács azon eleme, amelyhez nincs megfigyelt érték. Ha az elvárt napi rács, a megfigyelt napok halmaza, akkor
ahol a hiányzó napok halmaza. Egy elemről tehát nem eldönthető pusztán a hiánya, hogy mit jelent: lehet, hogy a folyamat aktív volt, de nulla esemény történt, és lehet, hogy a megfigyelés szakadt meg. Ez a két eset statisztikailag nem azonos. Az elsőnél a nulla valódi adat, a másodiknál ismeretlen állapot.
A különbség a gyakorlatban így jelenik meg: a
daily_counts sorozat indexe csak a megfigyelt napokat
tartalmazza, a daily_dense sorozat a teljes naptári rácsot,
ahol a hiányzó elemekre fill_value=0 került. A
daily_counts hossza legfeljebb 180, a
daily_dense hossza pontosan 180; a kettő közötti napok
halmaza
.
A 0-val kitöltés csak akkor megalapozott, ha az adatszerződés kimondja:
a megfigyelő rendszer minden nap aktív volt, és a napon nulla
rendelés érkezett. Ez a Northstar-lab generátoránál a rendelési
eseménysorozatra teljesül, de a mesterségesen kihagyott nap már nem
ilyen.
Trend. A trend a sorozat szintjének lassú, irányított változása. Ha a napi rendelések átlagos száma folyamatosan emelkedik az idővel, akkor az első 30 nap átlaga kisebb, mint az utolsó 30 napé. A trendet legkényelmesebben mozgóátlaggal közelítjük: például a 7 napos mozgóátlag
$$ \hat{T}t = \frac{1}{7}\left(Y{t-6} + Y_{t-5} + \cdots + Y_t\right),
$$
ahol a nap rendelési száma, és az ablakot gyakran a jelenlegi napig görgetjük. A mozgóátlag elsimítja a napon belüli véletlen ingadozást, és láthatóvá teszi a lassabb szintváltozást.
Szezonalitás (seasonality). A szezonalitás rögzített periódusú, ismétlődő minta. A napi rendelési adatban a legtermészetesebb periódus a hét napja: a hétvégi napokon eltérő lehet a rendelési volumen. A hét napja szerinti szezonális átlag
$$ \bar{Y}j = \frac{1}{N_j}\sum{t: \mathrm{dow}(t)=j} Y_t,
$$
ahol
a hét napjának kódja a DatetimeIndex.dayofweek szerint (0 =
hétfő, 6 = vasárnap),
pedig az adott nap előfordulásainak száma. A szezonális hatás nem oksági
magyarázat: csak annyit mond, hogy a hét egyes napjain szisztematikusan
más a szint.
Aggregálás (aggregation). Az idősor előállítása
aggregálás: a rendelési szintű táblából csoportosítjuk a sorokat a nap
szerint, majd minden csoportban összegzünk, átlagolunk vagy valamilyen
más összefoglaló értéket számolunk. A Northstar-példában a
size() az adott napi rendelések számát adja. Az aggregálás
előtti shape (2400, n_columns), utána (180,),
ha a teljes rácsot tekintjük; az általános szabály, hogy a csoportosítás
mindig csökkenti a megfigyelési egység számát.
Stacionaritási intuíció (stationarity intuition). Egy idősor akkor tekinthető gyenge értelemben stacionáriusnak, ha a statisztikai tulajdonságai nem függenek attól, hogy a sorozat melyik időablakát nézzük. A leggyakrabban használt közelítő feltételek:
ahol az idősor értéke a időpontban, és konstans, pedig csak a késleltetéstől függő autokovariancia. A feltétel azt fejezi ki, hogy a sorozat átlaga, varianciája és késleltetett kapcsolata azonos, ha az ablakot későbbre toljuk.
A napi rendelési sorozatunk csak akkor stacionárius, ha nincs szisztematikus trend, és a variancia sem változik az időben. A trend kimozdítja az átlagot az állandóságból; a hét napja szerinti szezonalitás azt jelenti, hogy a nap kódjától periodikusan függ, tehát nem állandó a teljes sorozaton. A gyakorlati ellenőrzés egyszerű formája: a mozgóátlag és a mozgóvariancia ne mutasson erős, irányított elmozdulást. Ez még nem formális teszt, de az intuícióhoz elegendő; a formális becslési és tesztelési kérdéseket később a megfelelő modellekkel kezeljük.
A példát a tanulólabor könyvtárából futtatjuk: lab/
munkakönyvtár, Linux/macOS bash vagy Windows PowerShell. A környezet a
lab pyproject.toml alapján uv sync-kel
telepíthető; a futtatáshoz uv run python szükséges.
import pandas as pd
from statsmodels.tsa.seasonal import seasonal_decompose
# Munkakönyvtár: lab/
orders = pd.read_csv("orders.csv")
# Az UTC-bélyegből naptári nap: az explicit +00:00 offsetet kezeljük,
# majd a napra normalizáljuk időzóna nélkül.
orders["order_day"] = (
pd.to_datetime(orders["order_ts"], utc=True)
.dt.tz_convert(None)
.dt.normalize()
)
# Napi aggregálás: ez a hiányos indexű sorozat.
daily_counts = orders.groupby("order_day").size()
# Teljes naptári rács 2025 első 180 napjára.
start_day = pd.Timestamp("2025-01-01")
end_day = pd.Timestamp("2025-06-29")
full_day_index = pd.date_range(start_day, end_day, freq="D")
# Explicit nullás sorozat: a rács minden napjához tartozik érték.
daily_dense = daily_counts.reindex(full_day_index, fill_value=0)
# Ellenőrző invariánsok.
assert len(full_day_index) == 180
assert daily_dense.notna().all()
assert (daily_dense.index == full_day_index).all()
print("dtype:", daily_dense.dtype, "| index dtype:", daily_dense.index.dtype)
print("sűrű sorozat hossza:", len(daily_dense))
# Mesterséges hiány: tegyük fel, hogy 2025-02-02-t a rendszer nem rögzítette.
daily_with_gap = daily_dense.drop(pd.Timestamp("2025-02-02"))
actual_gaps = full_day_index.difference(daily_with_gap.index)
print("hiányzó napok:", actual_gaps)
# Trend és szezonalitás szemléltető vizsgálata.
trend_7d = daily_dense.rolling(7).mean()
weekday_means = daily_dense.groupby(daily_dense.index.dayofweek).mean()
vol_28d = daily_dense.rolling(28).var()
print("7 napos mozgóátlag eleje:")
print(trend_7d.dropna().head(3))
print("héten belüli napátlagok (0=hétfő):")
print(weekday_means)
print("28 napos mozgóvariancia eleje és vége:")
print(vol_28d.dropna().head(1), vol_28d.dropna().tail(1))
decomp = seasonal_decompose(daily_dense, model="additive", period=7)
print("dekompozíciós trend vége:")
print(decomp.trend.dropna().tail(3))A seasonal_decompose az additív felbontást
alakban becsli, ahol
a trend,
a heti szezonális komponens,
a maradék; a period=7 a heti ciklust jelöli. A dekompozíció
itt leíró eszköz, nem előrejelzési modell.
A futtatási parancs Linux/macOS alatt:
cd lab
uv run python time_grid_example.pyWindows PowerShellben ugyanez a
cd lab; uv run python time_grid_example.py paranccsal
adható meg. A kód a lab definiált adatfájljait használja, külső
hálózatot nem igényel.
A daily_counts a rendelés sorok csoportosításából
keletkezik: az index csak azokat a napokat tartalmazza, amelyekhez
legalább egy rendelés tartozik. Ha minden nap érkezett rendelés, a
hossza 180; ha valamelyik nap nem, akkor rövidebb. A
daily_dense hossza minden esetben pontosan 180, mert a
reindex a teljes full_day_index-et használja.
A daily_dense.dtype int64, mert a
fill_value=0 egész szám; a
daily_dense.index.dtype datetime64[ns].
A mesterségesen kihagyott nap mutatja a két fogalom közötti
különbséget. A daily_dense 2025-02-02-ére 0 áll, mert az
előbbi feltételezés szerint a napon nulla rendelés volt. A
daily_with_gap indexéből ugyanez a nap hiányzik; a
full_day_index.difference(...) halmaz pontosan
{2025-02-02}. A hiányos nap tehát nem nulla érték, hanem
hiányzó megfigyelés: az elem hiányzik az indexből, ezért a sorozat
hossza csak 179.
A trend és a szezonalitás vizsgálata a gyakorlatban ilyen ellenőrzéseket ad:
A fenti kód kimenete a helyi futtatástól függ; az itt közölt sorok
szemléltető, nem mért kimenetek. A hallgató a saját futás során
ellenőrzi a három invariant: a sűrű sorozat hossza 180, nincs
NaN, és a hiányzó napok halmaza a szándékozott napot
tartalmazza.
Időindex-definíció: a hiányzó nap és a valóban nulla rendelést tartalmazó nap más állítás. Zérussal kitöltés csak akkor helyes, ha bizonyított a teljes adatgyűjtés; időzónaváltásnál és napra aggregálásnál az üzleti naptárnapot rögzíteni kell.
Értelmezés. Adott három nap: 2025-01-01 (4 rendelés), 2025-01-02 (7 rendelés), 2025-01-04 (5 rendelés). Milyen két különböző állítást tehetünk a 2025-01-03 napra, ha az adatszerződés szerint (a) a rendszer a napon aktív volt, de nem volt rendelés; (b) a rendszer aznap nem rögzített adatot? Melyik esetben szabad 0-val kitölteni a hiányt?
Megvalósítás. A orders.csv
beolvasása után készíts óránkénti sorozatot: az új megfigyelési egység
az óra, az érték az adott órában leadott rendelések száma. Használd a
teljes rácsot 2025-01-01 00:00-tól 2025-06-29 23:00-ig. Add meg az
explicit nullás sorozat hosszát, dtype-ját és a hiányzó órák számát.
Várt invariáns: a teljes órai rács hossza
,
a hiányzó órák száma legalább
,
mert legfeljebb 2400 különböző órában lehet rendelés.
Hibakeresés. A következő kódrészlet heti átlagot számol:
daily = orders.groupby("order_day").size()
daily_full = daily.reindex(full_day_index) # fill_value nélkül
weekly_avg = daily_full.resample("W").mean()Mi a hiba, és hogyan függ a javítás attól, hogy a hiányzó nap tényleges nulla vagy mérési hiány? Adj mindkét esetre javított kódot.
outcomes.csv ügyfélenként egyetlen
conversion_7d bináris értéket és
revenue_7d_minor összeget tartalmaz a 2025-07-01-től
2025-07-08-ig tartó teljes 7 napos ablakra. Helyes-e ebből napi
konverziós arány idősorozatot készíteni úgy, hogy a
conversion_7d értékét 7-tel osztjuk vagy naponként
ismételjük? Indokold, melyik megfigyelési szerződés sérül.Megoldás. Az (a) esetben a 2025-01-03-hoz tartozó érték tényleges nulla: a rendszer figyelte a napot, és nem érkezett rendelés. Ezt 0-val ki lehet tölteni, mert a 0 valódi adat. A (b) esetben a 2025-01-03 megfigyelés hiányzik: nem tudjuk, hány rendelés érkezett. A 0-val kitöltés hamis adatot gyárt, mert a napot nem figyelték. Ilyenkor a helyes kezelés vagy a nap elhagyása, vagy egy explicit hiány-modell, például imputáció; a konkrét Northstar-laborban a rendelési folyamat minden nap aktív, ezért ott a hiány nulla rendelést jelent, azonban ezt az adatszerződésből lehet tudni, nem a CSV-ből.
Megoldás. A kód:
orders["order_hour"] = (
pd.to_datetime(orders["order_ts"], utc=True)
.dt.tz_convert(None)
.dt.floor("h")
)
hourly_counts = orders.groupby("order_hour").size()
full_hours = pd.date_range(
"2025-01-01", "2025-06-29 23:00", freq="h"
)
hourly_dense = hourly_counts.reindex(full_hours, fill_value=0)
missing_hours = full_hours.difference(hourly_counts.index)
assert len(full_hours) == 4320
assert hourly_dense.notna().all()
assert len(missing_hours) == len(full_hours) - len(hourly_counts)
print(len(hourly_dense), hourly_dense.dtype, len(missing_hours))Az ellenőrző invariant: a sűrű sorozat hossza 4320, dtype-ja
int64; a hiányzó órák száma legalább 1920, mert egyedi óra
legfeljebb 2400 van. A tényleges szám valamivel nagyobb lehet, ha több
rendelés esik ugyanabba az órába.
reindex
fill_value=0 nélkül NaN értéket ad a hiányzó
napokra. A resample("W").mean() ezt a NaN-t
csendben figyelmen kívül hagyja; a heti átlag ezért nem a teljes hétre
vonatkozó összeg, hanem csak a megfigyelt napokra. A javítás attól függ,
mit jelent a hiány:daily_full = daily.reindex(full_day_index, fill_value=0)
weekly_avg = daily_full.resample("W").mean()daily_full = daily.reindex(full_day_index)
weekly_avg = daily_full.resample("W").mean() # NaN-t explicit ignorálA választott megoldás csak akkor elfogadható, ha a döntést az adatszerződésben rögzítjük.
outcomes.csv
megfigyelési egysége egy ügyfél végső 7 napos kimenete,
nem pedig egy ügyfél-nap pár. A conversion_7d azt jelzi,
hogy az ügyfél az egész ablak alatt konvertált-e; nem tartalmazza a
konverzió pontos napját. A 7-tel osztás vagy naponkénti ismétlés új
adatot gyárt, amely sem nem konverziós esemény, sem nem per-nap ráta. A
napi konverziós idősorhoz külön naponkénti eseményadat kellene, például
conversion_event_at; ilyen a jelenlegi kísérleti adatban
nincs. Az ITT-elemzés számára a helyes megfigyelés továbbra is a teljes
randomizált 300 ügyfél és a 7 napos kimenet.A leggyakoribb tévedés az, hogy a hiányzó megfigyelést automatikusan
nullának tekintjük. A fillna(0) mesterégesen csökkenti a
szintet: egy kételemes példában, ahol az első nap 10 rendelést látott, a
második nap pedig ismeretlen, a [10, 0] sorozat átlaga 5,
holott a második nap akár 10 rendelés is lehetett volna. A
dropna ezzel szemben azt állítja, hogy a második napról
nincs megfigyelés; az átlag akkor 10. Mindkét döntés állítás, nem
technikai művelet. Ez a Hiány és anomália mint
vizsgálati kérdés hiányzási szerződés időbeli változata: a
mechanizmus feltevése nem a táblából következik, hanem a mérési
folyamatból. A Nullmodell és p-érték
nullmodell-logikája is figyelmeztet: a szélsőségesség csak az előre
rögzített modell szerint értelmezhető, így az idősor hiányosságát is a
modellben kell rögzítenünk.
Önellenőrzés: mi a különbség a távollévő index és a nulla érték
között? Miért nem stacioner a növekvő trendes sorozat? Mit jelent egy
napi sorozat D frekvenciája? Ha a csúszó ablakos variancia
határozottan nő, mire következtetünk?
A nyers rendelés-időbélyeg szabálytalan gyakoriságú; a napi rácsra aggregálással szabályos frekvenciájú sorozatot kapunk. A hézag a teljes rács és a megfigyelt napok halmazkülönbsége; a hiányzó napokat csak akkor szabad nullával kitölteni, ha a mérési folyamat aktív volt és tényleg nulla esemény történt. A trend lassú irányított szintváltozás, a szezonalitás rögzített periódusú ismétlődés; a mozgóátlag és mozgóvariancia erős elmozdulása a stacionaritás sérülésére utal. A következő alkalmazásban erre az időtengelyre építünk cutoff előtti feature-képzést és gördülő visszatesztet, ahol a predikciós pillanat és a címke elérhetősége tovább szűkíti a megfigyelt ablakot.