9  Regressionsmodelle: Erweiterungen

Nach den basics für Regressionsmodelle sehen wir uns in diesem Kapitel einige hilfreiche Erweiterungen von Regressionsmodellen in R an.

library(tidyverse)

dat1 <- data.frame(id   = 1:8,
                   var1 = c(2,1,2,5,7, 8, 9,5),
                   var2 = c(2,2,1,9,7, 4,25,3),
                   educ = c(3,1,2,2,1, 3, 2,-1),
                   gend = c(2,1,1,2,1,2,1,2),
                   x    = c(2,1,2,4,1,NA,NA,NA) )
dat1$ed_fct <- factor(dat1$educ, levels = 1:3,
                        labels = c("basic","medium","high"))
dat1
  id var1 var2 educ gend  x ed_fct
1  1    2    2    3    2  2   high
2  2    1    2    1    1  1  basic
3  3    2    1    2    1  2 medium
4  4    5    9    2    2  4 medium
5  5    7    7    1    1  1  basic
6  6    8    4    3    2 NA   high
7  7    9   25    2    1 NA medium
8  8    5    3   -1    2 NA   <NA>

9.1 Nur vollständige Zeilen behalten

Wenn wir die beiden Modelle m1 und m3 vergleichen, sehen wir unterschiedliche Fallzahlen:

m1 <- lm(var2~ var1, data = dat1)  
m4 <- lm(var2 ~ ed_fct  + var1, dat1)
modelsummary(list("m1"=m1,"m4"=m4),gof_omit = "IC|RM|Log|F")
m1  m4
(Intercept) -2.340 -2.511
(4.345) (5.681)
var1 1.839 1.753
(0.773) (0.835)
ed_fctmedium 4.830
(6.038)
ed_fcthigh -3.253
(6.554)
Num.Obs. 8 7
R2 0.486 0.700
R2 Adj. 0.400 0.400

Es zeigt sich, dass in Modell m4 mit edu_fct 1 Fall verloren geht. Woran liegt das? Dazu lohnt sich ein Blick in die Daten:

dat1
  id var1 var2 educ gend  x ed_fct
1  1    2    2    3    2  2   high
2  2    1    2    1    1  1  basic
3  3    2    1    2    1  2 medium
4  4    5    9    2    2  4 medium
5  5    7    7    1    1  1  basic
6  6    8    4    3    2 NA   high
7  7    9   25    2    1 NA medium
8  8    5    3   -1    2 NA   <NA>

Die Angabe für ed_fct fehlt in für id = 8.

Um die Modelle zu vergleichen sollten wir also nur die Zeilen verwenden, für die auch die Werte für ed_fct vorliegen. Hier können wir diese Zeilen per Hand auswählen (und id=8 ausschließen), in größeren Datensätzen ist das aber mühsam.

9.1.1 complete.cases()

Hier hilft uns complete.cases(). Diese Funktion erstellt eine logische Variable, welche ein TRUE für alle vollständigen Fälle (also ohne ein NA). Unvollständige Fälle werden mit einem FALSE versehen. Dazu geben wir an, welche Variablen jeweils für diese Betrachtung berücksichtigt werden sollen und legen die Variable einfach im Datensatz als neue Spalte an. Für Modell 1 ist ein Fall complete, wenn var2 und var1 vorliegen. Wir wählen also mit select() die relevanten Variablen aus und wenden complete.cases auf diese Auswahl an:

dat1 %>% select(var1,var2) %>% complete.cases(.) 
[1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
dat1$compl_m1 <- dat1 %>% select(var1,var2) %>% complete.cases(.) 
dat1
  id var1 var2 educ gend  x ed_fct compl_m1
1  1    2    2    3    2  2   high     TRUE
2  2    1    2    1    1  1  basic     TRUE
3  3    2    1    2    1  2 medium     TRUE
4  4    5    9    2    2  4 medium     TRUE
5  5    7    7    1    1  1  basic     TRUE
6  6    8    4    3    2 NA   high     TRUE
7  7    9   25    2    1 NA medium     TRUE
8  8    5    3   -1    2 NA   <NA>     TRUE

Achtung: wenn wir keine Variablen angeben, werden NA aus allen Variablen berücksichtigt, hier also auch die NA aus x - die uns hier nicht interessieren:

dat1 %>% complete.cases(.) 
[1]  TRUE  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE
dat1$compl <- dat1 %>% complete.cases(.) 
dat1
  id var1 var2 educ gend  x ed_fct compl_m1 compl
1  1    2    2    3    2  2   high     TRUE  TRUE
2  2    1    2    1    1  1  basic     TRUE  TRUE
3  3    2    1    2    1  2 medium     TRUE  TRUE
4  4    5    9    2    2  4 medium     TRUE  TRUE
5  5    7    7    1    1  1  basic     TRUE  TRUE
6  6    8    4    3    2 NA   high     TRUE FALSE
7  7    9   25    2    1 NA medium     TRUE FALSE
8  8    5    3   -1    2 NA   <NA>     TRUE FALSE

…das gleiche machen wir für Modell m4, welches neben var2 und var1 ja auch noch ed_fct enthält:

dat1$compl_m4 <- dat1 %>% select(var1,var2,ed_fct) %>% complete.cases(.)

So sieht das dann im Datensatz aus:

dat1
  id var1 var2 educ gend  x ed_fct compl_m1 compl_m4
1  1    2    2    3    2  2   high     TRUE     TRUE
2  2    1    2    1    1  1  basic     TRUE     TRUE
3  3    2    1    2    1  2 medium     TRUE     TRUE
4  4    5    9    2    2  4 medium     TRUE     TRUE
5  5    7    7    1    1  1  basic     TRUE     TRUE
6  6    8    4    3    2 NA   high     TRUE     TRUE
7  7    9   25    2    1 NA medium     TRUE     TRUE
8  8    5    3   -1    2 NA   <NA>     TRUE    FALSE

9.1.2 Fälle mit missings finden

Jetzt können wir nach diesen Variablen filtern und uns diese Fälle genauer ansehen. Dazu filtern wir nach den Fällen, die zwar in m1 enthalten (also compl_m1 = TRUE) sind, aber nicht in m4 (compl_m4 = FALSE):

dat1 %>% filter(compl_m1 == T & compl_m4 == F) 
  id var1 var2 educ gend  x ed_fct compl_m1 compl_m4
1  8    5    3   -1    2 NA   <NA>     TRUE    FALSE

9.1.3 Modelle nur mit vollständigen Fällen berechnen

Außerdem können wir jetzt auch das Modell m1 so erstellen, dass wir nur die Fälle miteinbeziehen, die auch in Modell2 berücksichtigt werden:

m1_m4vars <- lm(var2 ~ var1     , data = filter(dat1,compl_m4 == T))
modelsummary(list("m1"=m1,"m1 mit m4vars"=m1_m4vars,"m4"=m4),gof_omit = "IC|RM|Log|F")
m1  m1 mit m4vars  m4
(Intercept) -2.340 -1.832 -2.511
(4.345) (4.646) (5.681)
var1 1.839 1.848 1.753
(0.773) (0.814) (0.835)
ed_fctmedium 4.830
(6.038)
ed_fcthigh -3.253
(6.554)
Num.Obs. 8 7 7
R2 0.486 0.508 0.700
R2 Adj. 0.400 0.409 0.400

Jetzt haben wir also in m1 mit m4vars und m4 die gleiche Fallzahl und können so die Ergebnisse direkt miteinander vergleichen.

9.2 Interaktionen

Interaktionen zwischen zwei Variablen können wir mit * berechnen:

dat1$g_fct <- factor(dat1$gend,levels = 1:2,
                     labels = c("women","men"))
m5 <- lm(var2 ~ var1 * g_fct, dat1)
summary(m5)

Call:
lm(formula = var2 ~ var1 * g_fct, data = dat1)

Residuals:
      1       2       3       4       5       6       7       8 
-1.5000  2.6145 -0.8827  4.5000 -7.3687 -1.5000  5.6369 -1.5000 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)  
(Intercept)    -3.1117     4.7702  -0.652   0.5498  
var1            2.4972     0.8211   3.041   0.0384 *
g_fctmen        5.9451     8.4973   0.700   0.5227  
var1:g_fctmen  -2.1639     1.5331  -1.411   0.2310  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 5.493 on 4 degrees of freedom
Multiple R-squared:  0.7244,    Adjusted R-squared:  0.5177 
F-statistic: 3.504 on 3 and 4 DF,  p-value: 0.1286

Interaktionen sollten immer visualisiert werden. Dafür ist {ggeffects} eine große Hilfe:

install.packages("ggeffects")
library(ggeffects)
Warning: Paket 'ggeffects' wurde unter R Version 4.2.2 erstellt
ggpredict(m5, terms = c("var1","g_fct[women,men]")) %>% plot()

# oder nebeneinander:
ggpredict(m5, terms = c("var1","g_fct[women,men]")) %>% plot(facet=TRUE)

Wir können diese Darstellung mit den bekannten {ggplot2}-Befehlen verändern:

ggpredict(m5, terms = c("var1","g_fct[women,men]")) %>% plot() + 
  scale_color_manual(values = c("orange","lightskyblue3"),breaks = c("women","men"),labels=c("Frauen","Männer")) +
  scale_fill_manual(values = c("orange","lightskyblue3"),breaks = c("women","men"),labels=c("Frauen","Männer")) +
  labs(title = "Vorhergesagte Werte für var2",
       color = "Gender",
       x = "Werte für var1",
       y = "Vorhergesagte Werte für var1")
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

9.3 Quadratische Terme & Polynome

m6 <- lm(var2 ~ var1 + I(var1^2), dat1 %>% filter(id != 7))
summary(m6)

Call:
lm(formula = var2 ~ var1 + I(var1^2), data = dat1 %>% filter(id != 
    7))

Residuals:
      1       2       3       4       5       6       7 
-0.5443  1.4334 -1.5443  3.2043  1.2713 -1.0248 -2.7957 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)
(Intercept)  -1.8580     3.4066  -0.545    0.614
var1          2.6481     1.9083   1.388    0.238
I(var1^2)    -0.2235     0.2110  -1.059    0.349

Residual standard error: 2.524 on 4 degrees of freedom
Multiple R-squared:  0.5099,    Adjusted R-squared:  0.2648 
F-statistic: 2.081 on 2 and 4 DF,  p-value: 0.2402
ggpredict(m6, terms = c("var1")) %>% plot()

9.4 Gewichtetes Regressionsmodell

library(survey)
etb18 <- haven::read_dta("./data/BIBBBAuA_2018_suf1.0.dta") 

etb18_k9 <- etb18 %>% filter(F518_SUF < 99998, zpalter < 100)
modx <- lm(F518_SUF ~ zpalter + I(zpalter^2),data=etb18_k9)

etb18_weighted <- svydesign(id      = ~intnr,
                            weights = ~gew2018,
                            data    = etb18_k9)
# family = gaussian() bekommen wir ein lineares Regressionsmodell, wie bei lm() - mit gewichtet
survey_modx <- svyglm(F518_SUF ~ zpalter + I(zpalter^2), 
                    family = gaussian(), data = etb18,design = etb18_weighted)

modelsummary(list("lm()"=modx,"svyglm()"= survey_modx),gof_omit = "RM|IC|Log")
lm() svyglm()
(Intercept) 586.343 118.307
(381.240) (338.854)
zpalter 110.543 121.639
(17.556) (17.635)
I(zpalter^2) -0.959 -1.116
(0.194) (0.213)
Num.Obs. 16543 16543
R2 0.008 0.013
R2 Adj. 0.008 0.013
F 64.956 109.810

9.5 “Robuste” Standardfehler

Häufig müssen die Standardfehler an Verstöße gegen die allgemeinen Annahmen (Homoskedastizität usw.) angepasst werden.

Die gute Nachricht ist, dass R eine ganze Reihe an Möglichkeiten bietet, Standard-Fehler zu korrigieren. Unter anderem mit {sandwich} oder {estimatr}.

Eine sehr einfache Variante ist die Korrektur von Standardfehlern in {modelsummary}, die wir uns etwas genauer ansehen:

Wir können sog. heteroskedasticity-consistent (HC) “robuste” Standardfehler mit der vcov-Option HC in modelsummary() anfordern. Die Hilfe-Seite für {modelsummary} bietet eine Liste mit allen Optionen.

Eine Option ist auch stata, um Ergebnisse aus Statas , robust zu replizieren. Hier mehr zu den Hintergründen und Unterschieden.

Basierend auf den oben eingelesenen Daten können wir folgendes Modell schätzen:

mod1 <- lm(F518_SUF ~ zpalter + I(zpalter^2),data=etb18_k9) 

library(modelsummary)
modelsummary(list(mod1,mod1,mod1,mod1),vcov = c("classical","HC","HC2","stata"),gof_omit = "RM|IC|Log")
 (1)   (2)   (3)   (4)
(Intercept) 586.343 586.343 586.343 586.343
(381.240) (373.854) (373.717) (373.614)
zpalter 110.543 110.543 110.543 110.543
(17.556) (18.052) (18.045) (18.040)
I(zpalter^2) -0.959 -0.959 -0.959 -0.959
(0.194) (0.207) (0.207) (0.207)
Num.Obs. 16543 16543 16543 16543
R2 0.008 0.008 0.008 0.008
R2 Adj. 0.008 0.008 0.008 0.008
F 64.956 82.649 82.622 82.634
Std.Errors IID HC HC2 HC1

9.6 Fixed effects Modelle mit {fixest}

install.packages("fixest")

{fixest}) bietet eine große Auswahl an Möglichkeiten: logistische FE-Modelle, mehr-dimensionale Fixed Effects, Multiway clustering, … Und es ist sehr schnell, bspw. schneller als Statas reghdfe. Für mehr Details empfiehlt sich die Vignette.

Die zentrale Funktion zur Schätzung linearer FE-Regressionsmodelle ist feols() - sie funktioniert ganz ähnlich zu lm(). Auch hier geben wir wieder eine Formel nach dem Schema abhängige Variabe ~ unabhängige Variable(n) an. Wir fügen lediglich mit | die Variable hinzu, welche die FEs festlegt:

library(fixest)
fe_mod1 <- feols(F518_SUF ~ zpalter + I(zpalter^2) | Bula, data = etb18_k9)
fe_mod1
OLS estimation, Dep. Var.: F518_SUF
Observations: 16,543 
Fixed-effects: Bula: 16
Standard-errors: Clustered (Bula) 
               Estimate Std. Error  t value   Pr(>|t|)    
zpalter      113.921974  14.227962  8.00691 8.5199e-07 ***
I(zpalter^2)  -0.989909   0.154147 -6.42187 1.1494e-05 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
RMSE: 3,509.1     Adj. R2: 0.014221
                Within R2: 0.008179

{fixest} clustert automatisch die Standardfehler entlang der FE-Variable (hier also Bula). Wenn wir das mal nicuth möchten, können wir mit der se-Option = "standard" ungeclusterte SE anfordern:

summary(fe_mod1, se = 'standard')
OLS estimation, Dep. Var.: F518_SUF
Observations: 16,543 
Fixed-effects: Bula: 16
Standard-errors: IID 
               Estimate Std. Error  t value   Pr(>|t|)    
zpalter      113.921974  17.517746  6.50323 8.0867e-11 ***
I(zpalter^2)  -0.989909   0.193688 -5.11085 3.2429e-07 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
RMSE: 3,509.1     Adj. R2: 0.014221
                Within R2: 0.008179
summary(fe_mod1, cluster = ~Bula)
OLS estimation, Dep. Var.: F518_SUF
Observations: 16,543 
Fixed-effects: Bula: 16
Standard-errors: Clustered (Bula) 
               Estimate Std. Error  t value   Pr(>|t|)    
zpalter      113.921974  14.227962  8.00691 8.5199e-07 ***
I(zpalter^2)  -0.989909   0.154147 -6.42187 1.1494e-05 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
RMSE: 3,509.1     Adj. R2: 0.014221
                Within R2: 0.008179

{modelsummary} zeigt die geclusterten SE:

modelsummary(list(fe_mod1),gof_omit = "R|IC|Log|F")
 (1)
zpalter 113.922
(14.228)
I(zpalter^2) -0.990
(0.154)
Num.Obs. 16543
Std.Errors by: Bula

9.7 Mehrebenenmodelle mit {lme4}

Mit lmer() können wir ein Random Intercept Modell berechnen, indem wir mit ( 1 | Bula) angeben, dass für Bula jeweils ein eigenes Random Intercept berechnet werden soll:

library(lme4)
Warning: Paket 'lme4' wurde unter R Version 4.2.2 erstellt
ml_m3 <- lmer(F518_SUF ~ zpalter + I(zpalter^2) + ( 1 | Bula), data=etb18_k9)
Warning: Some predictor variables are on very different scales: consider
rescaling
modelsummary(list(ml_m3),gof_omit = "R|IC|Log|F")
 (1)
(Intercept) 387.675
(392.748)
zpalter 113.680
(17.516)
I(zpalter^2) -0.988
(0.194)
SD (Intercept Bula) 365.875
SD (Observations) 3511.032
Num.Obs. 16543

Mehr zu Mehrebenenmodellen und {lme4} in Blogposts von Rense Nieuwenhuis und Tristan Mahr.

9.8 Anhang: Predictions mit marginaleffects und “manuelle” Darstellung

Wir hatten im Kapitel Interaktionen folgendes Modell geschätzt:

summary(m5)

Call:
lm(formula = var2 ~ var1 * g_fct, data = dat1)

Residuals:
      1       2       3       4       5       6       7       8 
-1.5000  2.6145 -0.8827  4.5000 -7.3687 -1.5000  5.6369 -1.5000 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)  
(Intercept)    -3.1117     4.7702  -0.652   0.5498  
var1            2.4972     0.8211   3.041   0.0384 *
g_fctmen        5.9451     8.4973   0.700   0.5227  
var1:g_fctmen  -2.1639     1.5331  -1.411   0.2310  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 5.493 on 4 degrees of freedom
Multiple R-squared:  0.7244,    Adjusted R-squared:  0.5177 
F-statistic: 3.504 on 3 and 4 DF,  p-value: 0.1286

Mit predictions() aus {marginaleffects} können wir basierend auf unserem Modell vorhergesagte Werte für bestimmte Werte berechnen. Dazu geben wir die die gewünschten mit einem expand.grid() an.

# Kombinationen aller Werte erstellen
expand.grid(var1 = 1:5, 
            g_fct =  c("women","men"))
   var1 g_fct
1     1 women
2     2 women
3     3 women
4     4 women
5     5 women
6     1   men
7     2   men
8     3   men
9     4   men
10    5   men

Diese Werte geben wir dann in predictions() als newdata = an:

library(marginaleffects)
p <- predictions(m5, 
                 newdata = expand.grid(var1 = 1:5,
                                       g_fct =  c("women","men")) )
                 
head(data.frame(p))
  rowid   estimate std.error  statistic      p.value  conf.low conf.high var1
1     1 -0.6145251  4.126052 -0.1489378 0.8816027065 -8.701438  7.472388    1
2     2  1.8826816  3.555532  0.5295076 0.5964533448 -5.086034  8.851397    2
3     3  4.3798883  3.099641  1.4130307 0.1576466975 -1.695297 10.455074    3
4     4  6.8770950  2.814641  2.4433298 0.0145524331  1.360501 12.393689    4
5     5  9.3743017  2.754104  3.4037579 0.0006646564  3.976358 14.772245    5
6     6  3.1666667  5.861938  0.5402082 0.5890534765 -8.322520 14.655853    1
  g_fct var2
1 women    2
2 women    2
3 women    2
4 women    2
5 women    2
6   men    2

Für den ggplot() verwenden wir dann geom_line() zusammen mit

  • geom_errorbar() für eine Darstellung mit Konfidenzintervallen als Error Bars
  • mit geom_ribbon() erhalten wir die Konfidenzintervalle als Konfidenzbänder (hier müssen wir mit alpha = die Deckkraft der etwas heruntersetzen und die Farbe mit fill= angeben um den Bereich einzufärben).
ggplot(data= data.frame(p),
       aes(x = var1, 
           y =  estimate, 
           ymin = conf.low,ymax = conf.high,
           color = g_fct)) + 
  geom_line() + 
  geom_errorbar(width = .1) +
  scale_color_manual(values = c("orange","lightskyblue3"),breaks = c("women","men"),labels=c("Frauen","Männer")) +
  labs(title = "Vorhergesagte Werte für var2",
       color = "Gender",
       x = "Werte für var1",
       y = "Vorhergesagte Werte für var1") +
  theme_minimal()
ggplot(data= data.frame(p),
       aes(x = var1, 
           y =  estimate, 
           ymin = conf.low,ymax = conf.high,
           color = g_fct, fill = g_fct)) + 
  geom_line() + 
  geom_ribbon(alpha = .1, color = NA) +
  scale_color_manual(values = c("orange","lightskyblue3"),breaks = c("women","men"),labels=c("Frauen","Männer")) +
  scale_fill_manual(values = c("orange","lightskyblue3"),breaks = c("women","men"),labels=c("Frauen","Männer")) +
  labs(title = "Vorhergesagte Werte für var2",
       color = "Gender", fill = "Gender",
       x = "Werte für var1",
       y = "Vorhergesagte Werte für var1") +
  theme_minimal()