6  REWB-Modelle

Die folgende Übung baut auf der Übung zu “Multilevel Regression” aus der Vorlesung “Anwendungsorientierte Analyseverfahren” auf. Diese Übung finden Sie hier. Die dort vorgestellte Analyse beschäftigt sich mit der Frage “Welche Facebook-Posts von Universitäten werden am meisten kommentiert?”.

In dieser Übung untersuchen wir ebenfalls, unter welchen Bedingungen Facebook-Posts von Universitäten mehr oder weniger kommentiert werden. Wir nutzen hierfür multilevel Regression (auch Mehrebenenmodell genannt), um die hierarchische Struktur der Daten (Posts innerhalb von Universitäten) zu berücksichtigen und uns anzuschauen, welchen Einfluss die Länge der jeweiligen Beiträge hat.

Quelle

Fähnrich, B., Vogelgesang, J., & Scharkow, M. (2020). Evaluating universities’ strategic online communication: How do Shanghai Ranking’s top 50 universities grow stakeholder engagement with Facebook posts? Journal of Communication Management, 24(3), 265–283. https://doi.org/10.1108/JCOM-06-2019-0090

library(lme4)
library(marginaleffects)
library(report)
library(tidyverse)
theme_set(theme_minimal())

faehnrich19 <- haven::read_sav("data/Faehnrich_2020.sav") |>
  haven::zap_labels()

faehnrich19
# A tibble: 10,807 × 20
  id             from_id uni   created_time        type  status_type likes_count
  <chr>            <dbl> <chr> <dttm>              <chr> <chr>             <dbl>
1 102471088936_… 1.02e11 Colu… 2013-12-19 19:08:19 link  shared_sto…          18
2 102471088936_… 1.02e11 Colu… 2013-03-08 15:48:25 video added_video          18
3 102471088936_… 1.02e11 Colu… 2013-03-05 21:33:03 link  shared_sto…          19
4 102471088936_… 1.02e11 Colu… 2015-01-15 20:37:08 photo added_phot…         309
5 102471088936_… 1.02e11 Colu… 2013-12-02 19:40:34 link  shared_sto…          27
# ℹ 10,802 more rows
# ℹ 13 more variables: comments_count <dbl>, shares_count <dbl>,
#   timeofday <chr>, topic_research <dbl>, topic_teaching <dbl>,
#   topic_awards <dbl>, topic_event <dbl>, topic_interact <dbl>,
#   topic_self <dbl>, word_count <dbl>, year <dbl>, uni_us <dbl>,
#   uni_fans <dbl>

6.1 Lineares Modell

Zunächst fitten wir ein einfaches lineares Modell, um den Zusammenhang zwischen der Anzahl der Wörter in einem Post (word_count) und der Anzahl der Kommentare (comments_count) zu untersuchen. Das Ergebnis zeigt, dass längere Posts tendenziell weniger Kommentare erhalten (Koeffizient: -0.03, p = 0.019).

lm(comments_count ~ word_count, data = faehnrich19) |>
  report::report_table() |>
  display()
Parameter Coefficient 95% CI t(10389) p Std. Coef. Std. Coef. 95% CI Fit
(Intercept) 13.22 (12.10, 14.35) 23.08 < .001 -2.55e-15 (-0.02, 0.02)
word count -0.03 (-0.06, -5.51e-03) -2.34 0.019 -0.02 (-0.04, -3.73e-03)
AIC 1.04e+05
AICc 1.04e+05
BIC 1.04e+05
R2 5.27e-04
R2 (adj.) 4.31e-04
Sigma 36.54

Was passiert, wenn wir uns das nach Uni aufgeteilt anschauen? Die folgende Abbildung zeigt lineare Regressionen für jede Universität separat. Es fallen deutliche Unterschiede zwischen den Universitäten auf. Dies deutet darauf hin, dass die Beziehung zwischen Wortzahl und Kommentaren uni-spezifisch variiert.

Achtung: In der Visualisierung fitten wir strenggenommen ein lineares Modell pro Gruppe. Dies ist nicht identisch zu einem Mehrebenenmodell, welches die hierarchische Struktur der Daten berücksichtigt (siehe nächster Abschnitt). Dies schauen wir uns nun im nächsten Schritt an.

faehnrich19 |>
  ggplot(aes(x = word_count, y = comments_count, color = uni)) +
  # geom_point() +
  geom_smooth(method = "lm") +
  labs(x = "Word count", y = "Predicted comments")

6.2 Lineares Modell mit Varying Intercepts

Für Details, siehe Übung aus der Vorlesung hier.

Um die Uni-spezifischen Unterschiede zu modellieren, fügen wir varying intercepts hinzu. Der Koeffizient für word_count (-0.05) repräsentiert nun den durchschnittlichen Effekt über alle Universitäten hinweg. Die Standardabweichung der Intercepte (16.62) zeigt, dass es nennenswerte Unterschiede in der Basiskommentarhäufigkeit zwischen Universitäten gibt. Das sehen wir auch, wenn wir die vorhergesagten Effekte als Average Marginal Effects visualisieren.

m_vi <-
  lmer(comments_count ~ 1 + word_count + (1 | uni), data = faehnrich19)

m_vi |>
  report::report_table() |>
  display()
Parameter Coefficient 95% CI t(10387) p Effects Group Std. Coef. Std. Coef. 95% CI Fit
(Intercept) 16.04 (10.87, 21.20) 6.08 < .001 fixed 0.06 (-0.08, 0.20)
word count -0.05 (-0.08, -0.02) -3.53 < .001 fixed -0.03 (-0.05, -0.02)
16.62 random uni
33.44 random Residual
AIC 1.03e+05
AICc 1.03e+05
BIC 1.03e+05
R2 (conditional) 0.20
R2 (marginal) 1.11e-03
Sigma 33.44
# Manuell, wie in der Übung zur Vorlesung:
# avg_predictions(m_vi, variables = c("word_count", "uni")) |>
#   as_tibble() |>
#   ggplot(aes(x = word_count, y = estimate, color = uni, group = uni)) +
#   geom_line(show.legend = FALSE) +
#   geom_point(show.legend = FALSE) +
#   labs(x = "Word count", y = "Predicted comments")

plot_predictions(m_vi, condition = c("word_count", "uni")) +
  labs(x = "Word count", y = "Predicted comments")

6.3 Lineares Modell mit Varying Intercepts und Slopes

Für Details, siehe Übung aus der Vorlesung hier.

Nun erweitern wir das Modell, indem wir auch varying slopes zulassen, d.h., der Effekt von word_count darf zwischen Universitäten variieren. Die Entscheidung darüber, ob wir auch varying slopes berücksichtigen wollten, sollte in der Regel theoriebasiert sein (also: erscheint es sinnvoll, dass die Effekte zwischen den Gruppen unterschiedlich stark sein sollten?).

Achtung: Hier erhalten wir eine Konvergenzwarnung. In der Praxis sollten wir das Modell vereinfachen (z.B. nur varying intercepts behalten), da die Daten möglicherweise nicht ausreichen, um die Slope-Varianz zuverlässig zu schätzen.

m_vivs <-
  lmer(comments_count ~ 1 + word_count + (1 + word_count | uni), data = faehnrich19)

m_vivs |>
  report::report_table() |>
  display()
Parameter Coefficient 95% CI t(10385) p Effects Group Std. Coef. Std. Coef. 95% CI Fit
(Intercept) 16.74 (7.10, 26.37) 3.41 < .001 fixed 0.06 (-0.08, 0.20)
word count -0.08 (-0.14, -0.01) -2.39 0.017 fixed -0.05 (-0.09, -0.02)
31.52 random uni
0.18 random uni
-0.92 random uni
33.33 random Residual
AIC 1.03e+05
AICc 1.03e+05
BIC 1.03e+05
R2 (conditional) 0.40
R2 (marginal) 2.07e-03
Sigma 33.33
summary(m_vivs)
Linear mixed model fit by REML ['lmerMod']
Formula: comments_count ~ 1 + word_count + (1 + word_count | uni)
   Data: faehnrich19

REML criterion at convergence: 102583.8

Scaled residuals: 
   Min     1Q Median     3Q    Max 
-2.283 -0.212 -0.076  0.008 44.478 

Random effects:
 Groups   Name        Variance  Std.Dev. Corr 
 uni      (Intercept) 9.936e+02 31.5214       
          word_count  3.252e-02  0.1803  -0.92
 Residual             1.111e+03 33.3276       
Number of obs: 10391, groups:  uni, 42

Fixed effects:
            Estimate Std. Error t value
(Intercept) 16.73979    4.91524   3.406
word_count  -0.07890    0.03301  -2.390

Correlation of Fixed Effects:
           (Intr)
word_count -0.830
optimizer (nloptwrap) convergence code: 0 (OK)
Model failed to converge with max|grad| = 2.79049 (tol = 0.002, component 1)
Model is nearly unidentifiable: very large eigenvalue
 - Rescale variables?
# Manuell, wie in der Übung zur Vorlesung:
# avg_predictions(m_vivs, variables = c("word_count", "uni")) |>
#   as_tibble() |>
#   ggplot(aes(x = word_count, y = estimate, color = uni, group = uni)) +
#   geom_line(show.legend = FALSE) +
#   geom_point(show.legend = FALSE) +
#   labs(x = "Word count", y = "Predicted comments")

plot_predictions(m_vivs, condition = c("word_count", "uni")) +
  labs(x = "Word count", y = "Predicted comments")

6.4 Zentrierung

Eine bessere inhaltliche Interpretation der Effekt können wir darüber hinaus durch unterschiedliche Varianten der Zentrierung ermöglichen.

6.4.1 Grand Mean

Bereits bekannt ist Ihnen vermutlich die Grand-Mean-Zentrierung (d.h., wir ziehen von jedem word_count die durschnittliche Anzahl an Wörtern über alle Posts hinweg ab). Durch Grand Mean-Zentrierung wird die Interpretation des Intercept im Modell vereinfacht. Der Intercept repräsentiert nun die erwartete Kommentarzahl für einen Post mit der durchschnittlichen Wortzahl (30.8 Wörter), welcher auf Basis des Modells 14.48 Kommentare erhalten sollte. Der durschnittliche Effekt von word_count bleibt inhaltlich gleich (-0.05).

faehnrich19 <-
  faehnrich19 |>
  mutate(
    word_count_grand_mean_centered = word_count - mean(word_count, na.rm = TRUE)
  )

faehnrich19 |>
  select(uni, starts_with("word_count")) |>
  slice(575:584) |>
  display()
uni word_count word_count_grand_mean_centered
Columbia U 23 -7.81
Columbia U 14 -16.81
Columbia U 33 2.19
Columbia U 23 -7.81
Columbia U 9 -21.81
Cornell U 20 -10.81
Cornell U 213 182.19
Cornell U 6 -24.81
Cornell U 14 -16.81
Cornell U 15 -15.81
lmer(comments_count ~ 1 + word_count_grand_mean_centered + (1 | uni), data = faehnrich19) |>
  report::report_table() |>
  display()
Parameter Coefficient 95% CI t(10387) p Effects Group Std. Coef. Std. Coef. 95% CI Fit
(Intercept) 14.48 (9.40, 19.57) 5.58 < .001 fixed 0.06 (-0.08, 0.20)
word count grand mean centered -0.05 (-0.08, -0.02) -3.53 < .001 fixed -0.03 (-0.05, -0.02)
16.62 random uni
33.44 random Residual
AIC 1.03e+05
AICc 1.03e+05
BIC 1.03e+05
R2 (conditional) 0.20
R2 (marginal) 1.11e-03
Sigma 33.44

6.5 Group Mean

Bei Group Mean-Zentrierung wird die Wortzahl innerhalb jeder Universität zentriert (group_by(uni)). Dies ermöglicht es, den within- (Effekt von Abweichungen vom Uni-Mittelwert) und between-Effekt (Effekt des Uni-Mittelwerts) getrennt zu modellieren.

faehnrich19 <-
  faehnrich19 |>
  group_by(uni) |>
  mutate(
    word_count_group_mean = mean(word_count, na.rm = TRUE),
    word_count_centered_within_group = word_count - word_count_group_mean
  ) |>
  ungroup() |>
  mutate(
    word_count_group_mean_centered = word_count_group_mean - mean(word_count_group_mean, na.rm = TRUE)
  )

faehnrich19 |>
  select(starts_with("word_count")) |>
  slice(575:584) |>
  display()
word_count word_count_grand_mean_centered word_count_group_mean word_count_centered_within_group word_count_group_mean_centered
23 -7.81 18.58 4.42 -12.24
14 -16.81 18.58 -4.58 -12.24
33 2.19 18.58 14.42 -12.24
23 -7.81 18.58 4.42 -12.24
9 -21.81 18.58 -9.58 -12.24
20 -10.81 33.34 -13.34 2.53
213 182.19 33.34 179.66 2.53
6 -24.81 33.34 -27.34 2.53
14 -16.81 33.34 -19.34 2.53
15 -15.81 33.34 -18.34 2.53

Wir können nun die Variable centered_within_group als “Within”-Effekt und, wenn gewünscht, gleichzeitig die Variable group_mean_centered als “Between”-Effekt aufnehmen und interpretieren. Im Beispielmodell sehen wir, dass der “Within”-Effekt (-0.05) signifikant ist (p < .001), der “Between”-Effekt (-0.12) jedoch nicht (p = .635). Dies bedeutet, dass Beiträge signifikant weniger Kommentare erhalten, wenn eine Universität längere Beiträge als üblich postet. Allerdings erhalten Universitäten, die allgemein längere Beiträge posten, nicht signifikant mehr oder weniger Kommentare. Falls für unsere Forschungsfrage relevant, könnten wir außerdem noch die Variable centered_within_group mit varying slopes spezifizieren (siehe oben) um zu sehen, ob auch die Stärke des Effekts (von längere Beiträge schreiben) sich zwischen den einzelnen Universitäten unterscheidet.

lmer(comments_count ~ 1 + word_count_centered_within_group + word_count_group_mean_centered + (1 | uni), data = faehnrich19) |>
  report::report_table() |>
  display()
Parameter Coefficient 95% CI t(10386) p Effects Group Std. Coef. Std. Coef. 95% CI Fit
(Intercept) 14.62 (9.39, 19.84) 5.48 < .001 fixed 0.07 (-0.08, 0.21)
word count centered within group -0.05 (-0.08, -0.02) -3.51 < .001 fixed -0.03 (-0.05, -0.01)
word count group mean centered -0.12 (-0.64, 0.39) -0.47 0.635 fixed -0.03 (-0.16, 0.10)
16.82 random uni
33.44 random Residual
AIC 1.03e+05
AICc 1.03e+05
BIC 1.03e+05
R2 (conditional) 0.20
R2 (marginal) 1.87e-03
Sigma 33.44

6.6 Funktion zur automatischen Zentrierung

Um die genannten Schritte zur Zentrierung nicht für jede Variable einzeln und manuell durchführen zu müssen, können wir eine Funktion definieren, die einen Großteil der Arbeit übernimmt:

rewb_centering <- function(d, group, vars) {
  d |>
    mutate(
      across(all_of(vars),
        ~ .x - mean(.x, na.rm = TRUE),
        .names = "{col}_grand_mean_centered"
      )
    ) %>%
    group_by({{ group }}) |>
    mutate(
      across(all_of(vars),
        ~ mean(.x, na.rm = TRUE),
        .names = "{col}_group_mean"
      ),
      across(all_of(vars),
        ~ .x - get(glue::glue("{cur_column()}_group_mean")),
        .names = "{col}_centered_within_group"
      )
    ) |>
    ungroup() |>
    mutate(
      across(all_of(str_c(vars, "_group_mean")),
        ~ .x - mean(.x, na.rm = TRUE),
        .names = "{col}_centered"
      )
    )
}
Profi-Tipp

Wenn wir mit Längsschnittdaten (Beobachtungen innerhalb Personen) arbeiten, können wir das Wort group an allen Stellen in der obigen Funktionsdefinition durch person ersetzen, um aussagekräftigere Variablenbezeichnungen zu erhalten.

Außerdem bennen wir alle Variablen, die wir zentrieren wollen, z.B.:

# manuell alle Variablen auflisten
vars <- c("likes_count", "comments_count", "shares_count", "word_count")

# oder über die select()-Funktion auswählen
vars <- faehnrich19 |>
  select(ends_with("count")) |>
  names()

vars
[1] "likes_count"    "comments_count" "shares_count"   "word_count"    

Nun können wir die Zentrierung in einem Schritt für alle Variablen in vars durchführen:

faehnrich19_zentriert <-
  faehnrich19 |>
  rewb_centering(uni, vars)

faehnrich19_zentriert |>
  select(starts_with("word_count")) |>
  slice(575:584) |>
  display()
word_count word_count_grand_mean_centered word_count_group_mean word_count_centered_within_group word_count_group_mean_centered
23 -7.81 18.58 4.42 -12.24
14 -16.81 18.58 -4.58 -12.24
33 2.19 18.58 14.42 -12.24
23 -7.81 18.58 4.42 -12.24
9 -21.81 18.58 -9.58 -12.24
20 -10.81 33.34 -13.34 2.53
213 182.19 33.34 179.66 2.53
6 -24.81 33.34 -27.34 2.53
14 -16.81 33.34 -19.34 2.53
15 -15.81 33.34 -18.34 2.53