Ebben az alfejezetben egy lineáris regressziós modellt úgy bővítünk,
hogy a magyarázó változó kategória és numerikus változó együtt
szerepeljen, ráadásul a kapcsolatuk csoportonként eltérő meredekséget
engedjen meg. A megfigyelési egység egy Northstar-rendelés; a cél a
delivery_days kézbesítési idő leírása a
segment (consumer vagy business) és a
distance_km alapján. Ez statisztikai, leíró modell, nem a
rendelés pillanatában érvényes predikciós szerződés: a Tanítás, validáció és érintetlen teszt–Predikciós szerződés és hibateszt fogja a
szivárgásmentes, időhelyes prediktív értékelést felépíteni.
A bemenet egy pandas DataFrame, amelynek minden sora egy rendelés,
legalább a következő oszlopokkal: delivery_days (pozitív
egész, nap), segment (két szintű kategória),
distance_km (nemnegatív lebegőpontos szám). Az induló
becslés, amit a modell ellenőrizhet: consumer ügyfélnél 10 km-en
körülbelül 3 nap a kézbesítés, 30 km-en 5 nap; business ügyfélnél
ugyanezeken a távolságokon 2, illetve 3 nap. Ezt a példa adatai
szándékosan a
,
illetve
összefüggésből generálják, így a kézi ellenőrzés pontos lesz.
A statsmodels formula API-ja a Patsy könyvtárat használja. A formula (képlet) karakterlánc, amely a modell szándékát írja le:
"delivery_days ~ distance_km"A ~ bal oldalán a válaszváltozó, jobb oldalán a
prediktorok állnak. A + új prediktort ad a modellhez, az
interceptet a statsmodels automatikusan hozzáadja, kivéve ha
-1-et írunk. Az OLS a lineáris algebra
és statisztika találkozásánál alfejezetben láttuk, hogy a
NumPy-megoldásnál az intercept oszlopot kézzel kellett létrehozni; a
formula API ezt elvégzi.
A kategorikus változót a C() függvénnyel jelöljük. A
Patsy alapértelmezése a treatment kódolás (treatment coding), más néven
dummy kódolás: az egyik szint referencia-csoport (reference category), a
többi szinthez egy-egy indikátorváltozó tartozik. Az indikátor 1, ha a
megfigyelés az adott csoportba esik, egyébként 0. A referencia-csoportot
explicit módon megadjuk, mert a Patsy ábécérend szerint választana, és a
business szint így kerülne alapértelmezett
referenciapozícióba:
"C(segment, Treatment(reference='consumer'))"A treatment kódolású modellben a referencia-csoport megfigyeléseihez minden indikátor értéke 0. Ezért az intercept ennek a csoportnak az alapértéke, a többi együttható pedig eltérés ehhez a csoporthoz képest.
Az interakció (interaction) a * operátorral írható. A
Patsy szabálya:
A * B = A + B + A:B
A : csak az interakciós tagot jelenti, főhatások nélkül.
A jelen modell:
"delivery_days ~ C(segment, Treatment(reference='consumer')) * distance_km"Ez három prediktort hoz létre: a kategorikus főhatást, a numerikus főhatást és a kettő szorzatát. A becsült egyenlet:
ahol
a business indikátor,
a distance_km kilométerben mért értéke. Az együtthatók
értelmezése:
delivery_days értéke
km-nél;A business csoport tényleges meredeksége tehát , nem pedig önmagában. Ez a különbségtétel a leggyakoribb értelmezési buktató.
A Patsy a formulából dizájnmátrixot (design matrix) épít. A statsmodels illesztett modellben az oszlopnevek a következőképpen jelennek meg:
Intercept
C(segment, Treatment(reference='consumer'))[T.business]
distance_km
C(segment, Treatment(reference='consumer'))[T.business]:distance_km
A T.business jelölés a treatment kódolás business
szintjét azonosítja. A mögöttes design_info objektum írja
le a kódolási szabályt; a gyakorlati ellenőrzéshez a
res.model.exog_names lista elegendő. A dizájnmátrix első
oszlopa csupa egyes, így a formula API modellje ugyanazt a geometriát
valósítja meg, mint az OLS a lineáris algebra és
statisztika találkozásánál alfejezetben kézzel felépített
mátrix.
A következő kód determinisztikus, zaj nélküli tanuló adatot hoz létre, hogy az együtthatók és a predikciók kézzel ellenőrizhetők legyenek. Az adatgeneráló összefüggés:
import numpy as np
import pandas as pd
import statsmodels.formula.api as smf
rows = [
("consumer", 10.0, 3.0),
("consumer", 20.0, 4.0),
("consumer", 30.0, 5.0),
("business", 10.0, 2.0),
("business", 20.0, 2.5),
("business", 30.0, 3.0),
]
df = pd.DataFrame(rows, columns=["segment", "distance_km", "delivery_days"])
mod = smf.ols(
"delivery_days ~ C(segment, Treatment(reference='consumer')) * distance_km",
data=df,
)
res = mod.fit()
print(res.params)
print(res.model.exog_names)Az elméletileg várt res.params kimenet:
Intercept 2.0
C(segment, Treatment(reference='consumer'))[T.business] -0.5
distance_km 0.1
C(segment, Treatment(reference='consumer'))[T.business]:distance_km -0.05
Az oszlopnevek listája:
['Intercept',
"C(segment, Treatment(reference='consumer'))[T.business]",
'distance_km',
"C(segment, Treatment(reference='consumer'))[T.business]:distance_km"]
A zaj nélküli konstrukció miatt a reziduumok elméletileg nullák; a
lebegőpontos számítás miatt legfeljebb
nagyságrendű eltérés várható. Ezen a hat soron a standard hibák és a
p-értékek nem értelmezhetők statisztikai következtetésre, mert nincs
mintavételi zaj; a summary() táblázatát itt csak a
formula-kódolás szemléltetésére használjuk.
Négy konkrét predikciót ellenőrzünk kézzel, a becsült együtthatókból:
new = pd.DataFrame({
"segment": ["consumer", "consumer", "business", "business"],
"distance_km": [10.0, 30.0, 10.0, 30.0],
})
print(res.predict(new))A predikciós képletek:
Az elméletileg várt res.predict(new) kimenet közelítőleg
[3.0, 5.0, 2.0, 3.0]. Ezt a futtatás után érdemes
np.allclose ellenőrzéssel összevetni.
A formula a transzformációt is közvetlenül tudja kezelni. Ha a távolság hatása nem lineáris, írhatunk például logaritmikus prediktort:
smf.ols(
"delivery_days ~ C(segment, Treatment(reference='consumer')) * np.log(distance_km)",
data=df,
)A np.log nevet a Patsy a hívó Python-környezetből oldja
fel. Ha hatványt vagy más kifejezést akarunk szó szerint értelmeztetni,
az I() as-is operátort használjuk, például
I(distance_km**2), mert a ^ karakter a
formulában interakciót jelöl, nem hatványozást. A transzformált változó
együtthatójának mértékegysége már nem nap per kilométer, hanem például
nap per log-kilométer; ezt az értelmezésnél mindig ki kell mondani.
A fenti modellben az együtthatók pontosan visszaadják az adatgeneráló
értékeket. A predikció ellenőrzésének invariánsa: ha a
res.params értékeit behelyettesítjük a fenti képletbe, a
kapott számnak egyeznie kell a res.predict(new)
kimenetével,
abszolút tolerancián belül. Emellett a referencia-csoport indikátora
nulla, ezért a consumer predikcióban nincs business-tag; ezt a
new DataFrame sorain is ellenőrizhetjük.
A Northstar orders.csv adatain ugyanez a modell már nem
lesz tökéletes, mert a valódi generált folyamat tartalmaz zajt, és a
távolságon kívül más tényezők is hatnak a kézbesítési időre. Az
együtthatók ekkor becslések, nem adatgeneráló paraméterek. A klasszikus
standard hibák ráadásul független, homoszkedasztikus hibákat
feltételeznek; a rendelések ügyfelekbe ágyazott szerkezete ezt sértheti.
A robusztus standard hibák és a diagnosztika a Diagnosztika és robusztus standard error témája
lesz.
Interakció értelmezése: a
distance_km:C(segment) tag nem önálló „kategóriahatás”,
hanem a távolság szerinti meredekség eltérése a referenciacsoporthoz
képest. A főhatás a numerikus változó nullértékénél értendő; ha ez
tartalmilag nem releváns, célszerű a távolságot központosítani.
A minimális példa becsült együtthatóival add meg a várható
delivery_days értéket consumer ügyfélre 15 km-nél és
business ügyfélre 15 km-nél. Fogalmazd meg szavakban, mit jelent a
interakciós együttható.
Töltsd be a lab/data/orders.csv fájlt a
projektgyökérből, és illessz modellt a
delivery_days ~ C(segment, Treatment(reference='consumer')) * distance_km
formulával. Ellenőrizd kézzel négy predikciót: consumer 10 km, consumer
40 km, business 10 km, business 40 km. A kézi számításhoz használd a
res.params értékeit, majd hasonlítsd össze a
res.predict kimenetével,
np.allclose(..., atol=1e-6) ellenőrzéssel.
Egy társ a segment * distance_km modell
distance_km együtthatójáról azt állítja, hogy az a távolság
állandó hatása mindkét szegmensben. Mi a hiba ebben az értelmezésben?
Adj számszerű ellenpéldát a minimális példa vagy a Northstar-modell
csoportonkénti meredekségeivel.
Illeszd újra a minimális példa modelljét úgy, hogy a
referencia-csoport business legyen:
C(segment, Treatment(reference='business')). Ellenőrizd,
hogy a négy predikció értéke változatlan marad, miközben az együtthatók
átíródnak. Add meg az új együtthatók elméleti értékeit, és indokold meg
az átparaméterezést.
Consumer 15 km-re:
Business 15 km-re:
A interakciós együttható azt mondja meg, hogy a business csoport meredeksége 0,05 nap/km-rel kisebb, mint a consumer referencia-csoport meredeksége. Nem azt jelenti, hogy a távolság hatása businessben : a business meredekség nap/km.
import numpy as np
import pandas as pd
import statsmodels.formula.api as smf
orders = pd.read_csv("lab/data/orders.csv")
mod = smf.ols(
"delivery_days ~ C(segment, Treatment(reference='consumer')) * distance_km",
data=orders,
)
res = mod.fit()
p = res.params
b0 = p["Intercept"]
b_b = p["C(segment, Treatment(reference='consumer'))[T.business]"]
b_d = p["distance_km"]
b_bd = p["C(segment, Treatment(reference='consumer'))[T.business]:distance_km"]
new = pd.DataFrame({
"segment": ["consumer", "consumer", "business", "business"],
"distance_km": [10.0, 40.0, 10.0, 40.0],
})
is_business = (new["segment"] == "business").astype(float)
d = new["distance_km"].to_numpy()
manual = b0 + b_b * is_business + (b_d + b_bd * is_business) * d
pred = res.predict(new).to_numpy()
print(p)
print("manual:", manual)
print("predict:", pred)
print("match:", np.allclose(manual, pred, atol=1e-6))A konkrét együtthatók a tanuló helyi futtatásától függenek, ezért itt
nem adunk előre rögzített számokat. Az ellenőrző invariant az, hogy a
manual és a predict tömbök
abszolút tolerancián belül egyeznek. A Northstar adatokon a modell nem
tökéletes, de ugyanaz a lineáris prediktor adja mindkét számítást; az
eltérés csak a kézi számítás kerekítési hibája lehet.
A hiba az, hogy az interakciós modellben a numerikus főhatás nem
általános hatás, hanem a referencia-csoport meredeksége. A minimális
példában a distance_km együtthatója
nap/km, de ez kizárólag a consumer csoport meredeksége. A business
csoport meredeksége
nap/km. Ha a társ a business csoportra is
nap/km hatást mond, akkor 10 km-en 1,0 nap helyett 0,5 nap
távolsághatást jósol, tehát a hiba számszerűen is látható. Az állandó
hatású modell a segment + distance_km formula lenne,
interakciós tag nélkül; az interakciós tag szignifikanciáját vagy a
csoportonkénti meredekségek különbségét külön kell vizsgálni.
mod2 = smf.ols(
"delivery_days ~ C(segment, Treatment(reference='business')) * distance_km",
data=df,
)
res2 = mod2.fit()
print(res2.params)
new = pd.DataFrame({
"segment": ["consumer", "consumer", "business", "business"],
"distance_km": [10.0, 30.0, 10.0, 30.0],
})
pred2 = res2.predict(new)
print(pred2)
print(res2.model.exog_names)Az új paraméterezés elméletileg várt együtthatói:
distance_km: business meredekség,
;A res2.predict(new) kimenete ismét
[3.0, 5.0, 2.0, 3.0] közelítőleg. Az ellenőrzés:
np.max(np.abs(pred2 - res.predict(new))) kisebb, mint
.
Az együtthatók tehát a referencia-csoport választásától függenek, a
modell által leírt függvény viszont ugyanaz marad. Ezért minden
közlésnél meg kell nevezni a referencia-csoportot; enélkül az intercept
és a főhatás értelmezhetetlen.
A leggyakoribb tévedés: „az interakciós együttható a numerikus változó hatása a másik csoportban”, vagy fordítva, „a főhatás minden csoportban állandó”. Az ellenpélda a fenti modell: a business csoport meredeksége nem , hanem ; a főhatás pedig nem közös hatás, hanem a consumer referencia-csoport meredeksége. A Közép, szóródás és robusztus összegzés csoportátlagos gondolkodásmódja itt is segít: mindig kérdezd meg, melyik csoportra vonatkozik az együttható.
Önellenőrző kérdések: melyik oszlop tartozik a referencia-csoporthoz,
és miért nulla ott az indikátor? Mit ad a predikció, ha a numerikus
változó értéke nulla? A res.model.exog_names listában
minden együtthatóhoz tartozik-e értelmezhető oszlop? Ha transzformált
prediktort használsz, meg tudod-e mondani az új mértékegységet? Ha a
referencia-csoportot megváltoztatod, változnak-e a predikciók? A
válaszok segítenek elválasztani a paraméterezési önkényt a modell
tartalmától.
A statsmodels formula API a Patsy segítségével explicit
referencia-csoportú treatment kódolást és interakciót kínál. A
C(segment, Treatment(reference='consumer')) * distance_km
képlet interceptet, csoportindikátort, numerikus főhatást és interakciós
tagot hoz létre. A főhatás a referencia-csoport meredeksége, az
interakció a másik csoport meredekségkülönbsége; a predikciót mindig a
becsült együtthatókból kézzel is ellenőrizhetjük. A kis példán a
predikciók pontosan visszaadják a generáló értékeket, a Northstar
adatokon pedig a kézi és a predict számítás egyezését
toleranciával igazoljuk. A transzformációk és a referencia-csoport csere
a paraméterezést változtatják, a illesztett függvényt nem. A következő
alkalmazás a modell diagnosztikája és robusztus standard hibák a Diagnosztika és robusztus standard error
alfejezetben.