A munkamenet célja, hogy a Pythonban már ismert kis táblaaggregációt
és egy lineáris regressziót R-ben reprodukáljunk, és közben pontosan
rögzítsük, melyik lépés mit csinál. Bemenet a Northstar szintetikus
labor lab/data/orders.csv fájlja; minden sor egy rendelés
(order_id), tehát a megfigyelési egység a rendelés. Az
oszlopok közül most a region, segment,
distance_km, priority_flag,
basket_minor és az utólag megfigyelt
delivery_days kell. Cél két kimenet: régiónkénti átlagos
kosárérték és késési arány, majd egy delivery_days kimenetű
lineáris modell. A modell itt oktatólagos asszociációs elemzés; nem a
rendelés pillanatában használható prediktív szerződés, mert a
delivery_days utólag ismert.
Induló becslés a kód futtatása előtt: a distance_km
együtthatója várhatóan pozitív, hiszen a távolság növekedése hosszabb
kézbesítést valószínűsít. A mini kontrollban a pontos meredekség 0,32
nap/km lesz; a labor nagyobb adatán ettől eltérő, de előjelében hasonló
értéket várunk. A basket_minor centben mért kosárérték
hatása valószínűleg kicsi, ezért inkább euróra skálázva érdemes a
modellbe tenni.
data.frame és tibbleA data.frame az R alap táblaszerkezete: oszlopokból álló
lista, ahol minden oszlop vektornak tekinthető. A tibble a
tidyverse modern táblája, amelyet a readr::read_csv() is
visszaad. A legfontosabb eltérés a karakterkezelés: a hagyományos
data.frame régebbi alapbeállításai karakteroszlopokat
faktorrá alakíthattak, a tibble azonban karakterként hagyja
meg őket. Ez a Northstar adatnál kritikus, mert a
customer_id szintetikus szöveges azonosító, a
region és a segment pedig kategória.
library(tibble)
d <- tibble(
x = c(1, 2, NA),
g = c("a", "b", "a"),
y = c(10, 20, 30)
)
d
is.character(d$g) # TRUE: a tibble nem alakította factorráA tibble nyomtatása csak az első sorokat mutatja, és az
oszloptípusokat is kiírja. Indexelésnél a d[1, ] mindig
tibble marad, a d[[1]] vagy d$x viszont
vektort ad. Ezt a dplyr-rel való munka közben nem kell
állandóan fejben tartani, de a Python pandas
Series/DataFrame kettősségéhez hasonló
fogalom.
A kategorikus változó R-ben külön típus: factor. A
faktor tárolja a lehetséges szinteket, és a szintek sorrendje
befolyásolja a statisztikai kódolást.
segment <- factor(c("consumer", "business", "consumer"))
levels(segment) # "business" "consumer" : ábécé szerint rendezveA Northstar segment csak consumer vagy
business lehet, a region pedig
north, central vagy south.
dplyr: a kis
aggregációA dplyr a táblaműveletek nyelvét adja. A natív
|> cső (pipe) a bal oldali eredményt teszi a jobb oldali
függvény első argumentumává. A leggyakoribb igék:
| Ige | Feladat | Pythonos analógia |
|---|---|---|
filter() |
sorok megtartása feltétel szerint | df.loc[mask] |
mutate() |
új vagy módosított oszlop | df.assign() |
group_by() |
csoportosítás | df.groupby() |
summarise() |
csoportonkénti összegzés | .agg() |
arrange() |
rendezés | .sort_values() |
select() |
oszlopok kiválasztása | df[cols] |
A summarise() a csoportosítás után minden csoportra
pontosan egy értéket ad; a mean() alapértelmezésben
na.rm = FALSE, tehát ha a csoportban bármely érték
NA, az összegzett átlag is NA lesz. Ezt a
hiányzó érték szakaszban részletezem.
Egy pillanatnyi kitérő a pénzegységre: a Northstar
basket_minor egész szám EUR-centben van. Az átlagos
kosárértéket euróban akkor kapjuk, ha a basket_minor / 100
oszlopot összegezzük. A centben maradó átlag a minor
mértékegységet örökíti tovább; a két szám nem felcserélhető.
lm és
kategorikus kódolásAz R formula szintaxisa a modell szerkezetét írja le: a
~ bal oldalán a célváltozó, jobb oldalán a magyarázó
változók szerepelnek. A
delivery_days ~ distance_km + priority_flag + segment
formula azt mondja,
Interpretációs figyelmeztetés. A regressziós
együttható csak a modellben szereplő többi változó szerinti feltételes
asszociáció. Causális hatásként kizárólag külön indokolt
azonosíthatósági feltevésekkel értelmezhető. hogy a kézbesítési napok
számát a távolsággal, a prioritásjelzővel és a szegmenssel magyarázzuk.
A + itt nem aritmetikai összeadás, hanem újabb modelltag
bevezetése; interakciót a : jelöl, a * pedig
az interakciót és a főhatásokat együtt adja.
A lm() az OLS-becslést végzi el:
ahol
a célváltozó vektora,
a magyarázó változók tervezett mátrixa,
a becsült paraméterek száma,
pedig a becsült együtthatók vektora. A
-edik
együttható standard hibája
,
a
-próba
statisztikája pedig
;
a reziduális szabadságfok
.
Ez ugyanaz a mennyiség, amelyet a Python statsmodels OLS-e
is jelent; a becsült értékek akkor egyeznek, ha a kódolás is
egyezik.
A kategorikus változónál az R lm() alapértelmezett
kezelési kontrasztot (treatment contrast) használ: a faktor ábécé
szerinti első szintje a referenciaszint, a többi szinthez egy-egy dummy
oszlop tartozik. A segment esetén a referenciaszint ezért
consumer, és a modellmátrixban egyetlen
segmentbusiness oszlop jelenik meg, amely a
business sorokban 1, a consumer sorokban 0. A
segmentbusiness együttható így a business és a consumer
szegmens átlagos eltérése a többi magyarázó változó rögzítése
mellett.
Az R-ben a hiányzó érték jelölése NA. Az alapvető
statisztikai függvények nem ugranak át rajta automatikusan:
mean(c(1, 2, NA)) eredménye NA, amíg a
na.rm = TRUE argumentumot meg nem adjuk. A
dplyr::filter(!is.na(oszlop)) a hiányos sorokat explicit
módon eldobja, a tidyr::drop_na() ugyanezt több oszlopra is
elvégzi.
Az lm() alapértelmezett na.action
beállítását a környezet options("na.action") értéke
határozza meg (a szokásos alapbeállítás na.omit): az
illesztés előtt eltávolítja azokat a sorokat, ahol bármely, a formulában
szereplő változó hiányzik. A felhasznált sorok számát a
nobs(modell) adja vissza, a reziduális szabadságfokot a
df.residual. Ha tehát a tiszta orders.csv 2400
teljes sort tartalmaz, a modell nobs értéke 2400; ha két
hiányzó távolság van, akkor 2398 — ezt a különbséget mindig ellenőrizni
kell.
A példa feltételezi, hogy a R-környezet és
statisztikai munkafolyamat alfejezetben létrehozott
lab/r renv-projekt aktív, és a readr meg a
dplyr telepítve van. A munkakönyvtár lab/r, a
CSV elérése ezért relatívan ../data/orders.csv.
Először a kézzel ellenőrizhető mini adat:
library(tibble)
mini <- tibble(
distance_km = c(5, 10, 15, 20),
delivery_days = c(3, 5, 6, 8)
)
mini_fit <- lm(delivery_days ~ distance_km, data = mini)
coef(mini_fit)
# Szemléltető kézi kontrollból:
# (Intercept) distance_km
# 1.5 0.32A kézi kontroll: a távolság átlaga km, a napok átlaga nap; a nevező , a számláló , tehát a meredekség nap/km, az intercept nap. A 20 km-re adott predikció nap, a tényleges 8 naphoz képest 0,1 nap maradék.
Most a Northstar labor aggregációja és modellje:
library(readr)
library(dplyr)
orders <- read_csv("../data/orders.csv") |>
mutate(
segment = factor(segment),
region = factor(region),
basket_eur = basket_minor / 100
)
region_agg <- orders |>
group_by(region) |>
summarise(
n_orders = n(),
avg_basket_eur = mean(basket_eur),
avg_delivery_days = mean(delivery_days),
late_rate = mean(target_late),
.groups = "drop"
)
model <- lm(
delivery_days ~ distance_km + priority_flag + segment + basket_eur,
data = orders
)
dir.create("results", showWarnings = FALSE)
write_csv(region_agg, "results/s146_region_agg.csv")
saveRDS(model, "results/s146_model.rds")
summary(model)A platformjelölt futtatás, ha a fenti kódot
scripts/s146_example.R néven mentjük:
# munkakönyvtár: lab/r
Rscript scripts/s146_example.RA labor kimenetét itt nem nevezem ellenőrzöttnek, mert a kapcsolódó példaprojektben még nem ellenőrzött kódot. Az alábbi ellenőrző feltételek viszont a futtatás után számszerűen tesztelhetők, és a mini kontroll kézi számítása pontos.
A mini modellben a distance_km együttható mértékegysége
nap/km: egy km távolságnövekedés a mintában átlagosan 0,32 nappal
hosszabb kézbesítéssel jár. Az intercept 1,5 nap az elméleti 0 km-hez
tartozó modellérték; ezt nem szabad tényleges üzleti előrejelzésként
értelmezni, mert 0 km-es rendelés a tartományon kívül van. A 7,9 napos
predikció a 20 km-es sorhoz a képletből adódik; a maradék 0,1 nap a
modell hibája.
A labor modellnél az ellenőrző invariánsok:
nrow(region_agg) értéke 3;
sum(region_agg$n_orders) értéke 2400, mert a tiszta
orders.csv minden sora teljes; model$rank
értéke 5, hiszen az intercept mellett distance_km,
priority_flag, segmentbusiness és
basket_eur paraméter van; df.residual(model)
értéke 2395. A tolerancia felesleges tizedesjegyekre: a mini meredekség
kerekítése 0,01 nap/km, a kézi kontrollban ezért a 0,32 pontos.
A segmentbusiness koefficiens a business szegmens és a
consumer referenciaszint közti átlagos különbség napban, a többi változó
rögzítése mellett. A basket_eur együtthatója nap/EUR, tehát
kis érték várható; ha a felhasználó a centben mért
basket_minor-t teszi a modelbe, az együttható 100-ad része
lesz, mert a magyarázó változó 100-szor nagyobb.
A kód most a target_late értéket csak a leíró
aggregációban használja, nem prediktorként. A prediktív Northstar
feladatban a delivery_days és a belőle képzett
target_late rendeléskori prediktorba tiltott; ez a fejezet
statisztikai asszociációt mutat be, nem prediktív szolgáltatást.
Értelmezés. A mini modell
summary(mini_fit) kimenetében a distance_km
együttható 0,32, a standard hibája körülbelül 0,028 nap/km, a t-érték
körülbelül 11,3. Mit jelent ez a három szám? Miért nem következik
belőle, hogy a távolság okságilag növeli a kézbesítési időt?
Megvalósítás. Futtasd a labor kódot, és írd ki a
summary(model) segmentbusiness sorának
becslését és standard hibáját. Ellenőrizd, hogy
sum(region_agg$n_orders) pontosan 2400, majd magyarázd meg,
miért ennyi.
Hibadiagnózis. Olvasd be az
../data/orders_raw.csv fájlt, és futtasd a
mean(orders_raw$distance_km) kifejezést. Az eredmény
NA. Miért? Javítsd a hiányzó érték kezelését, és add meg,
hány teljes sort használna az lm() a
delivery_days ~ distance_km modellnél, ha a
distance_km oszlopban két NA van.
Megváltozott feltétel. A labor modellben cseréld
le a basket_eur változót basket_minor-ra. Mi
történik a kosárérték együtthatójával és a standard hibájával? Ezután
állítsd a szegmens faktort
factor(segment, levels = c("business", "consumer"))
sorrendre, és futtasd újra a modellt. Mi lesz az új referenciaszint, és
hogyan változik az intercept meg a segmentconsumer
együttható?
A 0,32 azt becsli, hogy a távolság egy km-es növekedése a mintában átlagosan hány nappal hosszabb kézbesítéssel jár. A standard error 0,028 a becslés mintavételi bizonytalansága; a t-érték azt mutatja, hogy a becslés ennyi standard hibányira van a nullától. Ettől még ez korrelációs, nem oksági állítás: a mini adat nem kontrollálja az összes zavaró tényezőt, és a minta nem randomizált.
A futtatható ellenőrzés:
coef(model)["segmentbusiness"]
sqrt(diag(vcov(model)))["segmentbusiness"]
stopifnot(sum(region_agg$n_orders) == nrow(orders))Az összeg azért 2400, mert a tiszta orders.csv minden
sora egy rendelés, a group_by(region) három csoportot
képez, és a n() minden sort pontosan egyszer számol.
A mean() alapértelmezése na.rm = FALSE,
ezért bármely NA az átlagot is NA-vá teszi. A
javítás mean(distance_km, na.rm = TRUE) vagy
filter(!is.na(distance_km)). A
sum(is.na(orders_raw$distance_km)) értéke 2; az
lm() na.omit alapértelmezésével a 2405 sorból
2403 teljes sort használ. A negatív basket_minor és a nem
létező customer_id a távolságalapú modellnél nem hiányzó
sor, ezért nem dobódik el; az adatminőségi döntés külön lépés
marad.
Ha a basket_eur helyett basket_minor
szerepel, a magyarázó változó 100-szor nagyobb lesz, ezért az együttható
és a standard hibája is 100-ad részére csökken; a t-érték, a p-érték és
az intercept változatlan marad. A
factor(segment, levels = c("business", "consumer")) esetén
az új referenciaszint business; az új intercept a régi
intercept plusz a régi segmentbusiness együttható; az új
segmentconsumer együttható a régi
segmentbusiness negatívja. A modell predikciói soronként
ugyanazok maradnak.
A két nyelv hasonló nevű műveletei nem mindig azonos alapértelmezést
használnak. Az R mean() alapból nem hagyja ki a hiányzó
értéket, a Python pandas Series.mean() viszont
alapból igen.
mean(c(1, 2, NA)) # NAimport pandas as pd
pd.Series([1, 2, float("nan")]).mean() # 1.5A másik gyakori félreértés a kategorikus kódolás. Az R
lm() a consumer referenciaszinthez méri a
business eltérést, és egyetlen segmentbusiness
dummy oszlopot készít. A Python
pandas.get_dummies(drop_first=False) viszont mindkét
szintre oszlopot ad; ha egy sklearn lineáris modellt úgy
illesztünk, hogy a segment_consumer és a
segment_business oszlop is benne van, a kapott együtthatók
jelentése nem egyezik az R segmentbusiness kontrasztjával.
A statsmodels formula-illesztése viszont referenciaszintet
használ, ezért az R-hez közelebb áll. Mindig a
model.matrix() vagy a contrasts() kimenetével
ellenőrizd, hogy a kódolás ugyanazt a hipotézist becsüli-e.
Visszautalás: a kategorikus reprezentációról és a one-hot döntésről a Kategória, hiány és ferde változó, az OLS statisztikai értelmezéséről az OLS a lineáris algebra és statisztika találkozásánál, a hiányzó címkés értékelési ablakról pedig a Késő címke, teljesítmény és újratanítás szól.
segment egy
tibble-ben, és miért alakítja az lm() formula
mégis faktorrá?lm() modell, ha a formulában
szereplő egyik oszlopban 3 NA van egy 100 soros
adatban?segment faktorban melyik szint a referenciaszint
alapértelmezésben, és miért?A tibble karakteres oszlopokat nem faktorizál, a
dplyr csöves igéi pedig explicit, ellenőrizhető aggregációt
adnak. A formula a cél–prediktor viszonyt írja le az lm()
számára, amely OLS-t becsül és kezelési kontrasztot használ: az ábécé
szerinti első szint a referencia. A hiányzó NA az R-ben
explicit; az alapműveletek gyakran na.rm = TRUE beállítást
igényelnek, az lm() pedig a hiányos sorokat kihagyja. A
Python pandas alapértelmezései eltérhetnek, ezért hasonló
nevű kimeneteket csak a kódolás ellenőrzése után szabad
összehasonlítani. A következő Jelentés és nyelvek
közötti adatcsere ebből az aggregációból és modellből készít
R-jelentést, és rögzíti a Python–R közötti adatcsere formáját.