library(tidyverse) # tidyverse csomagok, select/filter/arrange/mutate/summarise, ggplot
library(broom.mixed) # tidy, glance, augment
library(performance) # modellösszehasonlítgatás
library(sjPlot) # modell becsléseinek ábrázolása
library(lme4) # hierarchikus / kevert lineáris modellek
library(glmmTMB) # sjplotnak kell
Az R beépített adathalmazai közül egyet, az iris-t fogjuk használni. Az iris méréseket tartalmaz virágfajtákról, amelyeknek három alfaja van, és négy dimenziójukat mérjük: a virág szirmának hosszát és szélességét, valamint a csészelevelek hosszát és szélességét. Az iris adathalmazt az idők hajnala óta használják arra, hogy gépi tanulást meg kategorizációt tanítsanak rá, mivel a fajták könnyen megkülönböztethetőek a méreteik alapján. Mi most lineáris modelleket fogunk rá építeni, mert erre a célra is tökéletesen megfelel.
str(iris)
## 'data.frame': 150 obs. of 5 variables:
## $ Sepal.Length: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
## $ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
## $ Petal.Length: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
## $ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
## $ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
head(iris)
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 5.1 3.5 1.4 0.2 setosa
## 2 4.9 3.0 1.4 0.2 setosa
## 3 4.7 3.2 1.3 0.2 setosa
## 4 4.6 3.1 1.5 0.2 setosa
## 5 5.0 3.6 1.4 0.2 setosa
## 6 5.4 3.9 1.7 0.4 setosa
Nézzük meg, hogyan viszonyulnak egymáshoz a különféle méretek.
# sepal length x width x species
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
geom_point() +
geom_smooth(method = 'lm', se = FALSE) +
theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'
# petal length x width x species
ggplot(iris, aes(x = Petal.Length, y = Petal.Width, color = Species)) +
geom_point() +
geom_smooth(method = 'lm', se = FALSE) +
theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'
# petal length x sepal length x species
ggplot(iris, aes(x = Petal.Length, y = Sepal.Length, color = Species)) +
geom_point() +
geom_smooth(method = 'lm', se = FALSE) +
theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'
# petal width x sepal width x species
ggplot(iris, aes(x = Petal.Width, y = Sepal.Width, color = Species)) +
geom_point() +
geom_smooth(method = 'lm', se = FALSE) +
theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'
A szirom (sepal) hossza összefügg a szélességével, a fellevél (petal) hossza is összefügg a szélességével, és a szirom és a fellevél hossza illetve szélessége is összefügg. De a szirom és a fellevél is nagyon más alakú attól függően, hogy milyen fajról beszélünk, ezért enélkül az információ nélkül nem tudjuk jól modellezni őket.
Több tidyverse itt. Több ggplot2 itt.
Most főleg a sepal width-et fogjuk jósolgatni a petal width-ből.
Szintaxis:
# sima lineáris modell: y = a + b * x
lm1 <- lm(Sepal.Width ~ Petal.Width, data = iris)
# többváltozós lineáris modell: faktor és numerikus prediktor: y = a1 / a2 / a3 + b * x
lm2 <- lm(Sepal.Width ~ Species + Petal.Width, data = iris)
# interakció: faktor és numerikus prediktor: y = a1 + b1 * x / a2 + b2 * x / a3 + b3 * x
lm3 <- lm(Sepal.Width ~ Species * Petal.Width, data = iris)
# interakció: két numerikus prediktor:
lm4 <- lm(Sepal.Width ~ Sepal.Length * Petal.Length, data = iris)
# persze egy lineáris modellben lehet akárhány prediktor:
lm5 <- lm(Sepal.Width ~ Petal.Width + Petal.Length + Sepal.Length + Species, data = iris)
Szintaxis:
# grouping faktor / random intercept:
lmm1 <- lmer(Sepal.Width ~ Petal.Width + (1 | Species), data = iris)
# grouping faktor és saját slope / random intercept és random slope:
lmm2 <- lmer(Sepal.Width ~ Petal.Width + (1 + Petal.Width | Species), data = iris)
Szintaxis:
# bináris válaszváltozó: binominalis eloszlás, logit linkfüggvény
iris$setosa = ifelse(iris$Species == "setosa", 1, 0)
glm1 <- glm(setosa ~ Sepal.Length, data = iris, family = binomial(link = "logit"))
# persze ez is lehet hierarchikus modell
glmm1 <- glmer(setosa ~ Sepal.Length + (1 | Species), data = iris, family = binomial(link = "logit"))
Több lme4 itt.
Öt modellt fogunk megnézni részletesebben. Ezek az lm1-3 és az lmm1-2. Emlékeztetőül:
formula(lm1)
## Sepal.Width ~ Petal.Width
formula(lm2)
## Sepal.Width ~ Species + Petal.Width
formula(lm3)
## Sepal.Width ~ Species * Petal.Width
formula(lmm1)
## Sepal.Width ~ Petal.Width + (1 | Species)
formula(lmm2)
## Sepal.Width ~ Petal.Width + (1 + Petal.Width | Species)
Megfeleltek a modellek a regresszió alapvetéseinek?
check_model(lm1)
Az első modellban maradékok eloszlása nagyjából normális, de a variancia nem konstans, szemmel láthatóan van még valami struktúra az adatokban, amit ez a modell nem talál meg.
check_model(lm2)
A második modellban megengedtük azt, hogy a lineáris modellnek a három fajra különböző interceptje legyen, de azt nem, hogy a slope is különböző legyen. A maradékok eloszlása és a variancia is javult, de nem tökéletes. A prediktorok közötti kollinearitás viszont elég súlyos: a három virágfajta méretei drasztikusan különböznek, ezért a species elég jól megragadja a petal width-et, azaz aggályos őket ugyanabban a modellban használni.
Vesd össze:
clm = lm(Sepal.Width ~ Species, data = iris)
tidy(clm) %>%
knitr::kable('simple', digits = 2)
| term | estimate | std.error | statistic | p.value |
|---|---|---|---|---|
| (Intercept) | 3.43 | 0.05 | 71.36 | 0 |
| Speciesversicolor | -0.66 | 0.07 | -9.69 | 0 |
| Speciesvirginica | -0.45 | 0.07 | -6.68 | 0 |
check_model(lm3)
A harmadik modellban megengedtük azt, hogy külön intercept és slope legyen a három fajra. Ez megoldja a maradékok eloszlásának a problémáit, viszont itt már gigantikus bajunk lesz azzal, hogy a két prediktor nagyon durván kollineáris (az interakció két olyan dolgot “szoroz össze”, amik már eleve ugyanazt mérik, tehát gáz van).
check_model(lmm1)
Az az alapvető baj, hogy minket a species hatása igazából most nem érdekel. Minket az érdekel, hogy ebben a virágtípusban a szirom hossza megjósolja-e a szirom szélességét. Viszont a species fontos eleme az adatok hierarchiájának: a különböző fajták eleve eltérő méretűek. Ezért a negyedik modellban betesszük a species-t grouping faktornak. Az eredmény hasonló az lm2-höz, a maradékok eloszlása és a variancia is jó, de nem tökéletes. A kollinearitással most nincsen córesz, mivel a species nem prediktor, hanem grouping factor.
check_model(lmm2)
Az ötödik modellban megengedjük, hogy a slope a grouping factor által meghatározott csoportokban különböző legyen. Így már elég szépek a maradékok. Ami azt illeti, olyan nagy javulás nem látszik az intercept-only modellhez képest. Itt sem kell kollinearitás miatt aggódnunk, mert a species nem fixed effect, hanem grouping factor.
Melyik modell a jobb?
Tudjuk, hogy az lm1 eleve aggályos, mert hülyén néznek ki a maradékok, az lm2 és az lm3 pedig konkrétan teljesen vállalhatatlanok, mert olyan kollineáris a két prediktor, hogy valószínűleg értelmezhetetlenek az estimate-ek: a modell hősiesen kiszámolt valamit, de amit kiszámolt, annak kevés köze van a valósághoz. Az interakcióról nem is beszélve. Hasonlítsuk össze az lm1-lmm1-lmm2-t.
compare_performance(lm1, lmm1, lmm2) %>%
knitr::kable('simple', digits = 2)
| Name | Model | AIC | AIC_wt | AICc | AICc_wt | BIC | BIC_wt | RMSE | Sigma | R2_conditional | R2_marginal | ICC | R2 | R2_adjusted |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| lm1 | lm | 159.96 | 0.00 | 160.13 | 0.00 | 169.00 | 0.00 | 0.4 | 0.41 | NA | NA | NA | 0.13 | 0.13 |
| lmm1 | lmerMod | 89.98 | 0.89 | 90.26 | 0.91 | 102.02 | 0.99 | 0.3 | 0.30 | 0.93 | 0.25 | 0.91 | NA | NA |
| lmm2 | lmerMod | 94.24 | 0.11 | 94.83 | 0.09 | 112.31 | 0.01 | 0.3 | 0.30 | 0.94 | 0.26 | 0.91 | NA | NA |
plot(compare_performance(lm1, lmm1, lmm2))
Úgy tűnik, hogy az lmm1 a legjobb modell. Őt nem csak ez az ábra indokolja, hanem az is, hogy tudjuk, hogy az lm1-ben hülyén néznek ki a maradékok, és azt is tudjuk, hogy valószínűleg azért, mert a megfigyeléseinket nagyban meghatározza, hogy a mérések melyik virágfajtához tartoznak, és az lm1 erről nem vesz tudomást. Nagyon hasonló módon érdemes rögtön hierarchikus / kevert modellel kezdeni, ha az adatok hierarchikussága egyértemű: pl egy kísérletben több válasz tartozik egy-egy emberhez, vagy egy felmérésben több ember pontszáma egy-egy iskolához, stb.
Biztonság kedvéért hasonlítsuk össze csupán a két hierarchikus modellt egymással.
compare_performance(lmm1, lmm2) %>%
knitr::kable('simple', digits = 2)
## Some of the nested models seem to be identical and probably only vary in
## their random effects.
| Name | Model | AIC | AIC_wt | AICc | AICc_wt | BIC | BIC_wt | R2_conditional | R2_marginal | ICC | RMSE | Sigma |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| lmm1 | lmerMod | 89.98 | 0.89 | 90.26 | 0.91 | 102.02 | 0.99 | 0.93 | 0.25 | 0.91 | 0.3 | 0.3 |
| lmm2 | lmerMod | 94.24 | 0.11 | 94.83 | 0.09 | 112.31 | 0.01 | 0.94 | 0.26 | 0.91 | 0.3 | 0.3 |
plot(compare_performance(lmm1, lmm2))
## Some of the nested models seem to be identical and probably only vary in
## their random effects.
A két hierarchikus (kevert) modell közül is az lmm1 tűnik jobbnak, főleg azoknak a mérőszámoknak az alapján, amelyke a komplexitást büntetik (AIC, BIC). Csináljunk egy likelihood ratio tesztet, ami megmondja, hogy melyik modell illeszkedik jobban az adatokra:
test_likelihoodratio(lmm1,lmm2) %>%
knitr::kable('simple', digits = 2)
## Some of the nested models seem to be identical and probably only vary in
## their random effects.
| Name | Model | df | df_diff | Chi2 | p | |
|---|---|---|---|---|---|---|
| lmm1 | lmm1 | lmerMod | 4 | NA | NA | NA |
| lmm2 | lmm2 | lmerMod | 6 | 2 | 0.34 | 0.84 |
A p-value 0.05 fölött van, tehát a lmm2 nem illeszkedik szignifikánsan jobban az adatokra, mint a lmm1. A BIC és az AIC is kisebb az lmm1 esetén, tehát ez elég egyértelmű.
Vesd össze: ugyanez a teszt azt mondja, hogy az lmm1 jobb, mint az lm1, hiába sokkal bonyibb.
test_likelihoodratio(lm1,lmm1) %>%
knitr::kable('simple', digits = 2)
| Name | Model | df | df_diff | Chi2 | p | |
|---|---|---|---|---|---|---|
| lm1 | lm1 | lm | 3 | NA | NA | NA |
| lmm1 | lmm1 | lmerMod | 4 | 1 | 71.98 | 0 |
Több modellkritika itt.
summary(lmm1)
## Linear mixed model fit by REML ['lmerMod']
## Formula: Sepal.Width ~ Petal.Width + (1 | Species)
## Data: iris
##
## REML criterion at convergence: 83.7
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -3.8786 -0.6281 0.0294 0.6323 2.8523
##
## Random effects:
## Groups Name Variance Std.Dev.
## Species (Intercept) 0.92709 0.9629
## Residual 0.09047 0.3008
## Number of obs: 150, groups: Species, 3
##
## Fixed effects:
## Estimate Std. Error t value
## (Intercept) 2.1524 0.5747 3.745
## Petal.Width 0.7546 0.1197 6.303
##
## Correlation of Fixed Effects:
## (Intr)
## Petal.Width -0.250
A summary függvény kiirogatja az R konzolba a modell fő paramétereit. Ez jó, csak nehéz berakni mondjuk egy táblázatba.
A tidy függvény kevesebb információt közöl, de takarosabb.
tidy(lmm1, conf.int = T) %>%
knitr::kable('simple', digits = 2)
| effect | group | term | estimate | std.error | statistic | conf.low | conf.high |
|---|---|---|---|---|---|---|---|
| fixed | NA | (Intercept) | 2.15 | 0.57 | 3.75 | 1.03 | 3.28 |
| fixed | NA | Petal.Width | 0.75 | 0.12 | 6.30 | 0.52 | 0.99 |
| ran_pars | Species | sd__(Intercept) | 0.96 | NA | NA | NA | NA |
| ran_pars | Residual | sd__Observation | 0.30 | NA | NA | NA | NA |
Ábrázoljuk a modell becsléseit.
plot_model(lmm1, 'est')
Hát itt egy “b” van, a petal width, szóval ez nem túl érdekes. Nézzünk meg egy modellt, amiben sok van.
plot_model(lm5, 'est')
Vigyázat: ha a különféle prediktorok nem ugyanazon a skálán vannak,
akkor ez jó hülyén fog kinézni. Itt ez egyszerűen eleve adott (mindegyik
valami méret), illetve a scale() függvény segítségével is átalakíthatunk
prediktorokat (pred = scale(pred)).
Ábrázoljuk a modell predikcióit.
plot_model(lmm1, 'pred')
Predikciók ábrázolása több prediktor esetén. A
plot_model mindig a kimeneti változóra tett jóslatot teszi
az y tengelyre, az első megadott term-et (prediktort) teszi az x
tengelyre, és a többi megadott term szerint bont. Ha a második
(harmadik) term egy folyamatos változó, akkor csinál belőle három
bödönt, és ábrázolja azokat. (Itt valszeg mind a két példának
használt modell teljesen értelmetlen jóslatokat tesz, de arra jók,
hogy gyakoroljuk ezeket a függvényeket.)
plot_model(lm3, 'pred', terms = c('Petal.Width','Species'))
plot_model(lm3, 'pred', terms = c('Species','Petal.Width'))
plot_model(lm4, 'pred', terms = c('Petal.Length','Sepal.Length'))
plot_model(lm4, 'pred', terms = c('Sepal.Length','Petal.Length'))
Hierarchikus / kevert modellek esetén ábrázolhatjuk a random intercepeket és slope-okat is.
plot_model(lmm1, 're')
plot_model(lmm2, 're')
Több sjplot itt.
Itt nagyon sok dologról nem volt szó. A kurzus oldalán található linkgyűjtemény jó kiindulási pontot ad a folytatáshoz.