-
Notifications
You must be signed in to change notification settings - Fork 60
Expand file tree
/
Copy pathWeek10_LMM_Moderation.qmd
More file actions
1473 lines (1102 loc) · 51.3 KB
/
Week10_LMM_Moderation.qmd
File metadata and controls
1473 lines (1102 loc) · 51.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
title: "PSY4210: Linear Mixed Models (LMMs) - Moderations & Comparisons"
author:
- name: Pei Hwa Goh
- name: Joshua F. Wiley
email: joshua.wiley@monash.edu
- name: Michelle Byrne
date: "`r Sys.Date()`"
format:
html:
toc: true
number-sections: true
embed-resources: true
anchor-sections: true
smooth-scroll: true
---
These are the `R` packages we will use.
```{r setup}
options(digits = 4)
## emmeans is a new package
library(data.table)
library(JWileymisc)
library(extraoperators)
library(lme4)
library(lmerTest)
library(multilevelTools)
library(visreg)
library(ggplot2)
library(ggpubr)
library(haven)
library(emmeans)
## load data collection exercise data
## merged is a a merged long dataset of baseline and daily
dm <- as.data.table(read_sav("[2021] PSY4210 merged.sav"))
## Remind R which of our variables are factors
dm[, sex := factor(
sex,
levels = c(1,2),
labels = c("male", "female"))]
dm[, relsta := factor(
relsta, levels = c(1,2,3),
labels = c("single", "in a committed exclusive relationship", "in a committed nonexclusive relationship"))]
```
# LMM Notation
Let's consider the formula for a relatively simple LMM:
$$
y_{ij} = b_{0j} + b_1 * x_{1j} + b_2 * x_{2ij} + \varepsilon_{ij}
$$
Here as before, the *i* indicates the *i*th observation for a specific
unit (e.g., person but the unit could also be classrooms, doctors,
etc.) and the *j* indicates the *j*th unit (in psychology usually
person).
Regression coefficients, the $b$s with a *j* subscript indicate a
fixed and random effect. That is, the coefficient is allowed to vary
across the units, *j*. As before, these coefficients in practice are
decomposed into a fixed and random part:
$$
b_{0j} = \gamma_{00} + u_{0j}
$$
and we estimate in our LMM the fixed effect part, $\gamma_{00}$, and
the variance / standard deviation of the random effect or the
covariance matrix if there are multiple random effects, $\mathbf{G}$:
$$
u_{0j} \sim \mathcal{N}(0, \mathbf{G})
$$
Regression coefficients without any *j*
subscript indicate fixed only effects, effects that do not vary across
units, *j*. These are fixed effects and get estimated directly.
Predictors / explanatory variables, the $x$s with an *i* subscript
indicate that the variable varies within a unit. Note that the
outcome, $y$ **must** vary within units to be used in a LMM.
In this case, the notation tells us the following:
- $y_{ij}$ the outcome variable, which varies both within and between
people
- $b_{0j}$ the intercept regression coefficient, which is both a fixed
and random effect
- $b_1$ the regression coefficient, slope, for the first predictor,
which is a fixed effect only
- $x_{1j}$ the first predictor/explanatory variable, this is a between
unit variable only, as the lack of an *i* subscript indicates it
does not vary within units. It could not have a random slope.
- $b_2$ the regression coefficient, slope, for the second predictor,
which is a fixed effect only
- $x_{2ij}$ the second predictor/explanatory variable, this variable
varies within individuals as shown by its *i* subscript. It could
have a random slope, although in this model, it only has a fixed
effect slope.
- $\varepsilon_{ij}$ the residuals, these vary within and between
units.
The following decision tree provides some guide to when a predictor /
explanatory variable can be a fixed and random effect.
```{r, echo = FALSE, fig.cap = "Type of effect decision tree"}
DiagrammeR::grViz('
digraph "Type of effect decision tree" {
graph [overlap = true, fontsize = 12]
node [fontname = Helvetica, shape = rectangle]
variable [ label = "What level does your variable vary at?" ];
between [ label = "Between Variable" ];
within [ label = "Within Variable" ];
fixed [ label = "Fixed Effect Only" ];
random [ label = "Fixed & Random Effect" ];
type [ label = "Do you want a fixed effect only?" ];
variable -> between [ label = "only between units" ];
variable -> within [ label = "varies within (+/- between) units" ];
between -> fixed ;
within -> type ;
type -> fixed [ label = "yes" ];
type -> random [ label = "no" ];
}
')
```
Let's see two examples of putting this basic model into practice.
$$
energy_{ij} = b_{0j} + b_1 * loneliness_{j} + b_2 * stress_{ij} + \varepsilon_{ij}
$$
The corresponding `R` code is:
```{r}
summary(lmer(dEnergy ~ loneliness + dStress + (1 | ID), data = dm))
```
Here is another example decomposing stress into a between and within component.
$$
energy_{ij} = b_{0j} + b_1 * Bstress_{j} + b_2 * Wstress_{ij} + \varepsilon_{ij}
$$
```{r}
dm[, c("Bstress", "Wstress") := meanDeviations(dStress), by = ID]
summary(lmer(dEnergy ~ Bstress + Wstress + (1 | ID), data = dm))
```
We can make more effects random effects. For example, taking our
earlier example and just changing $b_2$ into $b_{2j}$:
$$
energy_{ij} = b_{0j} + b_1 * loneliness_{j} + b_{2j} * stress_{ij} + \varepsilon_{ij}
$$
The corresponding `R` code is:
```{r}
summary(lmer(dEnergy ~ loneliness + dStress + (dStress | ID), data = dm))
```
Now with two random effects, we assume that the random effects,
$u_{0j}$ and $u_{2j}$, which we collectively denote
$\mathbf{u}_{j}$ follow a multivariate normal distribution with
covariance matrix $\mathbf{G}$.
$$
\mathbf{u}_{j} \sim \mathcal{N}(0, \mathbf{G})
$$
Based on the little decision chart, between unit only variables, like
$loneliness_j$ and $Bstress_j$ *cannot* be random effects. In the
data collection exercise, we measured loneliness at baseline and also
in the daily diary questionnaires. In this example we are using the
baseline (trait) loneliness and not the daily one. Also, while it is
technically possible for something to only be a random effect without
a corresponding fixed effect, its not common and not recommended as it
would be equivalent to assuming that the fixed effect, the mean of the
distribution, is 0, which is rarely appropriate.
# Interactions in LMMs
Interactions in LMMs work effectively the same way that interactions
in GLMs do, although there are a few nuances in options and possible
interpretations.
Using the notation from above, let's consider a few different possible
interactions.
## Cross Level (Between and Within Unit) Interactions
First, let's take our model with loneliness and stress and
include an interaction. Here is the model without an interaction.
$$
energy_{ij} = b_{0j} + b_1 * loneliness_{j} + b_2 * stress_{ij} + \varepsilon_{ij}
$$
The corresponding `R` code is:
```{r}
summary(lmer(dEnergy ~ loneliness + dStress + (1 | ID), data = dm))
```
Now let's add the interaction, as a fixed effect.
$$
energy_{ij} = b_{0j} + b_1 * loneliness_{j} + b_2 * stress_{ij} +
b_3 * (loneliness_{j} * stress_{ij}) +
\varepsilon_{ij}
$$
The corresponding `R` code is:
```{r}
## long way
summary(lmer(dEnergy ~ loneliness + dStress + loneliness:dStress + (1 | ID), data = dm))
## short hand in R for simple main effect + interaction
## identical, but shorter to the above
summary(lmer(dEnergy ~ loneliness * dStress + (1 | ID), data = dm))
```
The relevant, new, part is the interaction term, $b_3$, a fixed effect
in this case. If we focus just on that one term, we see that the
coefficient, $b_3$ is applied to the arithmetic product of two
variables, here loneliness and stress. As it happens, one of them, loneliness,
varies between units whereas the other, stress, varies within
units. You will sometimes see this termed as "cross level" interaction
between it involves a between and within varying variable.
$$
b_3 * (loneliness_{j} * stress_{ij})
$$
As with interactions for regular GLMs, interactions in LMMs can be
interpretted in different ways. The two common interpretations are
easiest to see by factoring the regression equation.
Here are three equal equations that highlight different ways of
viewing the interaction.
In the latter two formats, it highlights how the simple effect of
stress varies by loneliness and how the simple effect of loneliness varies by
stress.
$$
\begin{align}
energy_{ij} &= b_{0j} + b_1 * loneliness_{j} + b_2 * stress_{ij} + b_3 * (loneliness_{j} * stress_{ij}) + \varepsilon_{ij} \\
&= b_{0j} + b_1 * loneliness_{j} + (b_2 + b_3 * loneliness_j) * stress_{ij} + \varepsilon_{ij} \\
&= b_{0j} + (b_1 + b_3 * stress_{ij}) * loneliness_{j} + b_2 * stress_{ij} + \varepsilon_{ij} \\
\end{align}
$$
The nuance in LMMs comes in because some variables vary only between
units and others within units. For example, when interpretting the
interaction with respect to the simple effect of stress, we could say
that the association between daily stress and energy on the same day
depends on the loneliness of a participant. Conversely, when interpretting
with respect to the simple effect of loneliness, we could say that the
association of participant loneliness and average energy depends on how
stressed someone is feeling on a given day. Loneliness varies between
people, stress varies within people, so that must be taken into
account in the interpretation.
## Between Unit Interactions
The same approach would work with other type of variables in LMMs. For
example, here we have a model with loneliness and sex as predictors. Both
vary only between units.
$$
\begin{align}
energy_{ij} &= b_{0j} + b_1 * loneliness_{j} + b_2 * sex_{j} + b_3 * (loneliness_{j} * sex_{j}) + \varepsilon_{ij} \\
&= b_{0j} + b_1 * loneliness_{j} + (b_2 + b_3 * loneliness_j) * sex_{j} + \varepsilon_{ij} \\
&= b_{0j} + (b_1 + b_3 * sex_{j}) * loneliness_{j} + b_2 * sex_{j} + \varepsilon_{ij} \\
\end{align}
$$
When interpretting the interaction with respect to the simple effect of
sex, we could say that the association between participant sex and
average energy depends on the loneliness of a participant. Conversely,
when interpretting with respect to the simple effect of loneliness, we
could say that the association of participant loneliness and average
energy depends on participant's sex.
## Within Unit Interactions
Finally, both variables could vary within units.
$$
\begin{align}
energy_{ij} &= b_{0j} + b_1 * selfesteem_{ij} + b_2 * stress_{ij} + b_3 * (selfesteem_{ij} * stress_{ij}) + \varepsilon_{ij} \\
&= b_{0j} + b_1 * selfesteem_{ij} + (b_2 + b_3 * selfesteem_{ij}) * stress_{ij} + \varepsilon_{ij} \\
&= b_{0j} + (b_1 + b_3 * stress_{ij}) * selfesteem_{ij} + b_2 * stress_{ij} + \varepsilon_{ij} \\
\end{align}
$$
When interpretting the interaction with respect to the simple effect
of stress, we could say that the association between daily stress and
energy on the same day depends on same day self-esteem level.
Conversely, when interpretting with respect to the simple effect of
self-esteem, we could say that the association of daily self-esteem
and same day energy depends on how stressed someone is feeling on a
given day.
# Continuous Interactions in `R`
Aside from the notes about some minor interpretation differences, in
general interactions in LMMs are analysed, graphed, and interpreted
the same way as for GLMs.
First to avoid any issues around diagnostics etc. from haven labeled
type data, we will convert the variable we are going to work with to
numeric. Then we fit a LMM with an interaction between stress and
neuroticism, energy as the outcome and a random intercept as the only
random effect.
```{r}
dm[, dSE := as.numeric(dSE)]
dm[, dMood := as.numeric(dMood)]
dm[, dEnergy := as.numeric(dEnergy)]
dm[, dStress := as.numeric(dStress)]
dm[, neuroticism := as.numeric(neuroticism)]
m <- lmer(dEnergy ~ neuroticism * dStress + (1 | ID), data = dm)
```
A quick check of the model diagnostics suggests that the
data look fairly good. The intercepts do not appear to
follow a normal distribution that closely, partly due to
the long left tail, but for now we will leave it. If you're interested
in how to fix left skews, please see the bonus material at the end of
this markdown (optional).
```{r}
plot(modelDiagnostics(m), nrow = 2, ncol = 2, ask = FALSE)
```
No extreme values are present. If there were any, we can remove that,
as we have discussed in depth in previous lectures, update the model,
and re-run diagnostics. In practice it could take a few rounds of
extreme value removal or you may decide to stop at one round.
Let's look at the summary of our model, *m*. Although we have used
`summary()` a lot in the past, we'll introduce another function to
help look at `lmer()` model results, `modelTest()`. In this lecture,
we will only learn and interpret part of its output, with the rest
of the output from `modelTest()` covered later. In addition to get
nicely formatted results rather than a set of datasets containing
the results, we use the `APAStyler()` function.
```{r}
APAStyler(modelTest(m))
```
The results show the regression coefficients, asterisks for p-values,
and 95% confidence intervals in brackets for the fixed effects, the
standard deviations of the random effects, the model degrees of
freedom, which is how many parameters were estimated in the model
total, and the number of people and observations. For now, we will
ignore all the output under row 9, N (Observations). In the case of
this model we can see the following.
A LMM was fit with 283 observations from 88 people. There was no
significant neuroticism x stress interaction, b [95% CI] = 0.07
[-0.03, 0.16], p = .160.
We can also pass multiple model results in a list together, which puts the
results side by side. This is particularly helpful for comparing
models with and without covariates, to evaluate whether removing
extreme values changed the results substantially, or to compare models
with different outcomes.
```{r}
# Remember: m <- lmer(dEnergy ~ neuroticism * dStress + (1 | ID), data = dm)
# Here's a new model with mood as the outcome to compare:
mtest1 <- lmer(dMood ~ neuroticism * dStress + (1 | ID), data = dm)
APAStyler(list(
Energy = modelTest(m),
Mood = modelTest(mtest1) ))
```
These results show us that we have similar results when predicting
daily energy and daily mood from stress and neuroticism. The
relationship between daily stress and daily mood, and daily stress
and daily energy did not vary by individual differences in
neuroticism. In this case, it would make sense to re-run these
models without the interaction term to test the main effects
of daily stress and neuroticism.
```{r}
mmain <- lmer(dEnergy ~ neuroticism + dStress + (1 | ID), data = dm)
mtest1main <- lmer(dMood ~ neuroticism + dStress + (1 | ID), data = dm)
APAStyler(list(
Energy = modelTest(mmain),
Mood = modelTest(mtest1main) ))
```
Results are indeed similar for daily mood and energy as outcomes.
There are significant negative associations between daily stress
and both outcome variables, and also neuroticism and both outcome
variables.
## Plotting
Typically, to plot our significant interaction, a few exemplar
lines are graphed showing the slope of one variable with the
outcome at different values of the moderator.
As with GLMs, we can use the `visreg()` function.
Here, we'll use neuroticism as the moderator. A common
approach to picking level of the moderator is to use the
Mean - 1 SD and Mean + 1 SD. To do that, we first need the mean
and standard deviation of neuroticism, which we can get using
`egltable()` after excluding duplicates by ID, since
neuroticism only varies between units. Note that we are doing
this for the sake of example only since our interaction was
not signficant.
```{r}
egltable(c("neuroticism"), data = dm[!duplicated(ID)])
visreg(m, xvar = "dStress",
by = "neuroticism", overlay=TRUE,
breaks = c(3.46-1.11, 3.46+1.11),
partial = FALSE, rug = FALSE)
```
The results show, in an easier to interpret way, what the positive
interaction coefficient of $b = 0.07$ means, people with higher levels
of neuroticism are less sensitive to the (negative) effects of stress.
People higher in neuroticism are relatively less sensitive to the
effects of stress, although in both cases, higher stress is associated
with lower energy levels. Keep in mind again that we are interpreting
this as though the interaction was signficant for the sake of example
only.
Another common way of picking some exemplar values is to use the 25th
and 75th percentiles. These work particularly well for very skewed
distributions where the mean +/- SD could be outside the observed
range of the data. Again we exclude duplicates by ID and then use the
`quantile()` function to get the values, 3, and 4.5 for the 25th and
75th percentiles.
```{r}
quantile(dm[!duplicated(ID), neuroticism], na.rm = TRUE)
visreg(m, xvar = "dStress",
by = "neuroticism", overlay=TRUE,
breaks = c(3, 4.5),
partial = FALSE, rug = FALSE)
```
## Simple Effects
When working with models that have interactions, a common aid to
interpretation is to test the simple effects / slopes from the
model. For example, previously we graphed the association between
stress and energy at M - 1 SD and M + 1 SD on neuroticism.
However, although visually both lines appeared to have a
negative slope, we do not know from the graph alone whether there is a
significant association between stress and energy at both the
low (M - 1 SD) and high (M + 1 SD) levels of neuroticism. To answer
that, we need to test the simple slope of stress at specific values of
neuroticism. Again this is for demonstration only given our non-
significant moderation. We would not need to compute simple slopes
or effects for non-significant moderations in reality.
Our default model does actually give us one simple slope:
it is the simple slope of stress when $neuroticism = 0$. However, as
we can tell from the mean and standard deviation of neuroticism, 0 is
very far outside the plausible range of values so that simple slope
given to us by default from the model is not too useful. We could
either center neuroticism and re-run the model, which would get us a
different simple slope, or use post hoc functions to calculate simple
slopes.
We will use the `emtrends()` function from the `emmeans` package to
test the simple slopes. This function also works with GLMs, for your
reference.
The `emtrends()` function take a model as its first argument, then the
variable that you want to calculate a simple slope for, here `stress`,
the argument `at` requires a list of specific values of the moderator,
and then we tell it how we want degrees of freedom calculated (note
this only applies to `lmer` models). We store the results in an `R`
object, `mem` and then call `summary()` to get a summary table. The
`infer = TRUE` argument is needed in `summary()` if you want
p-values.
```{r}
mem <- emtrends(m, var = "dStress",
at = list(neuroticism = c(3.46-1.11, 3.46+1.11)),
lmer.df = "satterthwaite")
summary(mem, infer=TRUE)
```
The relevant parts of the output, for us, are the columns for
`stress.trend` which are the simple slopes, the values of
`neuroticism` which tell us at what values of neuroticism we have
calculated simple slopes, the confidence intervals, `lower.CL` and
`upper.CL`, 95% by default, and the p-value. From these results, we
can see that when $neuroticism = 2.35$ there is a significant
negative association between stress and energy, but not when
$neuroticism = 4.57$.
## Sample Write Up
With all of this information, we can plan out some final steps for a
polished write up of the results. First, let's get exact p-values for
all our results. We can do this through options to `pcontrol` in
`APAStyler()`. We also re-print the simple slopes here.
```{r}
APAStyler(modelTest(m),
pcontrol = list(digits = 3, stars = FALSE, includeP = TRUE,
includeSign = TRUE, dropLeadingZero = TRUE))
summary(mem, infer=TRUE)
```
Now we will make a polished, finalized figure. I have customized the
colours, and turned off the legends. In place of legends, I have
manually added text annotations including the simple slopes and
confidence intervals and p-values for the simple slopes^[For your
reference, it took about 8 trial and errors of different x and y
values and angles to get the text to line up about right. I did not
magically get the values to use to get a graph that I thought looked
nice. That is why I think sometimes it is easier to add this sort of
text after the fact in your slides or papers rather than building it
into the code.].
```{r}
visreg(m, xvar = "dStress",
by = "neuroticism", overlay = TRUE,
breaks = c(3.46-1.11, 3.46+1.11),
partial = FALSE, rug = FALSE, gg = TRUE,
xlab = "Daily Stress",
ylab = "Predicted Daily Energy") +
scale_color_manual(values = c("2.35" = "black", "4.57" = "grey70")) +
theme_pubr() +
guides(colour = "none", fill = "none") +
annotate(geom = "text", x = 3.2, y = 3.9, label = "High Neuroticism: b = -0.07 [-0.23, 0.10], p = .447",
angle = -9) +
annotate(geom = "text", x = 4, y = 4.4, label = "Low Neuroticism: b = -0.21 [-0.36, -0.06], p = .005",
angle = -22)
```
A linear mixed model using restricted maximum likelihood was used to
test whether the association of daily stress on daily energy is
moderated by baseline neuroticism scores. All predictors were included
as fixed effects and a random intercept by participant was included.
Visual diagnostics showed that energy was normally distributed, and no
outliers were present.
The daily stress x neuroticism interaction was not statistically
significant, which indicated that the relationship between stress
and energy did not vary by neuroticism. Results from the analysis with
the interaction dropped revealed that both neuroticism and daily stress
were negatively associated with daily energy.
# Continuous x Categorical Interactions in `R`
Continuous x Categorical interactions are conducted much as continuous
x continuous interactions. Typically with continuous x categorical
interactions, simple slopes for the continuous variable are calculated
at all levels of the categorical variable.
Let's illustrate this with a model examining the relationship between
daily energy levels and self-esteem with sex as a moderator.
```{r}
mconcat <- lmer(dSE ~ dEnergy*sex + (1| ID), data = dm)
```
The model diagnostics look relatively good, albeit not perfect.
```{r}
plot(modelDiagnostics(mconcat), nrow = 2, ncol = 2, ask = FALSE)
```
With reasonable diagnostics, we can look at a summary.
There is one extreme residual but I'm choosing to
leave it in the dataset.
```{r}
summary(mconcat)
```
Factor variables in interactions do not work currently with
`modelTest()`, so if we wanted to use it, we'd need to manually dummy
code the categorical variable. The results are identical.
```{r}
dm[, female := as.integer(sex == "female")]
malt <- lmer(dSE ~ dEnergy * female + (1 | ID), data = dm)
APAStyler(modelTest(malt))
```
## Plotting
With continuous x categorical interactions, the easiest approach is to
plot the simple slope of the continuous variable by the categorical
one as shown in the following.
```{r}
visreg(mconcat, xvar = "dEnergy",
by = "sex", overlay=TRUE,
partial = FALSE, rug = FALSE)
```
## Simple Effects
When working with models that have interactions, a common aid to
interpretation is to test the simple effects / slopes from the
model. For example, previously we graphed the association between
daily energy and self-esteem at each level of the categorical
`sex` variable, i.e. for men and women.
However, we cannot tell from the graph whether daily energy is
significantly associated with self-esteem for men or women.
To answer that, we need to test the simple slope of energy at
the two sex levels. Our default model does actually give us one simple slope:
it is the simple slope of energy when for men (i.e when female = 0), but we
might want more.
We will use the `emtrends()` function from the `emmeans` package to
test the simple slopes.
The `emtrends()` function take a model as its first argument, then the
variable that you want to calculate a simple slope for, here `energy`,
the argument `at` requires a list of specific values of the moderator,
and then we tell it how we want degrees of freedom calculated (note
this only applies to `lmer` models). We store the results in an `R`
object, `mem` and then call `summary()` to get a summary table. The
`infer = TRUE` argument is needed in `summary()` if you want
p-values.
```{r}
mem <- emtrends(mconcat, var = "dEnergy",
at = list(sex = c("male", "female")),
lmer.df = "satterthwaite")
summary(mem, infer=TRUE)
```
The relevant parts of the output, for us, are the columns for
`dEnergy.trend` which are the simple slopes, the values of
`sex` which tell us at what values of sex we have calculated
simple slopes, the confidence intervals, `lower.CL` and
`upper.CL`, 95% by default, and the p-value. From these results,
we can see that daily energy is significantly associated with
self-esteem for any sex, although it is stronger for male participants than
female participants.
# Categorical x Categorical Interactions in `R`
Categorical x Categorical interactions are conducted comparably,
although more contrasts / simple effect follow-ups are possible.
Here we will work with Int_Str again, which is a two-level
categorical predictor (0 = no interaction with a stranger, 1 =
interacted with a stranger that day) and energy as the outcome.
We also work with a three-level conscientiousness variable.
```{r}
## create a categorical conscientiousness variable
dm[, cons3 := cut(conscientiousness, breaks = quantile(conscientiousness, probs = c(0, 1/3, 2/3, 1), na.rm=TRUE),
labels = c("Low", "Mid", "High"),
include.lowest = TRUE)]
mcat2 <- lmer(dEnergy ~ cons3 * Int_Str + (1 | ID), data = dm)
```
The model diagnostics look relatively good.
```{r}
plot(modelDiagnostics(mcat2), nrow = 2, ncol = 2, ask = FALSE)
```
With reasonable diagnostics, we can look at a summary.
```{r}
summary(mcat2)
```
## Plotting
With categorical x categorical interactions, `visreg()` produces OK
but not great figures as shown in the following. We can see the means
of energy for all 6 cells (the cross of 3 level of
conscientiousness x 2 levels of Int_Str).
```{r}
dm[, Int_Str := factor(
Int_Str,
levels = c(0,1),
labels = c("No interaction with stranger", "Interacted with stranger"))]
visreg(mcat2, xvar = "Int_Str",
by = "cons3", overlay=TRUE,
partial = FALSE, rug = FALSE)
```
## Simple Effects
When working with two categorical interactions (or with
a categorical predictor with >2 levels where you want to test various
group differences), the `emmeans()` function from the `emmeans`
package is helpful. We can get the means of interaction with stranger by
conscientiousness group and get confidence intervals and p-values. These p-values
test whether each mean is different from zero, by default.
```{r}
## re-run mcat2 now with int_str as factor
mcat2 <- lmer(dEnergy ~ cons3 * Int_Str + (1 | ID), data = dm)
em <- emmeans(mcat2, "Int_Str", by = "cons3",
lmer.df = "satterthwaite")
summary(em, infer = TRUE)
```
A nice plot, with confidence intervals for the fixed effects, can be
obtained by using the `emmip()` function from the `emmeans`
package. It takes as input the results from `emmeans()`, not the
`lmer()` model results directly. Here is a simple plot showing the
categorical interactions. Note that with this approach, you could
basically fit the same model(s) that you would with a repeated
measures or mixed effects ANOVA model, with the advantage that LMMs do
not require balanced designs and allow both categorical and continuous
predictors (e.g., you could include continuous covariates
easily). GLMs and (G)LMMs can do everything that t-tests and various
ANOVAs can, but with greater flexibility.
```{r}
emmip(em, cons3~Int_Str, CIs = TRUE) +
theme_pubr() +
ylab("Predicted Energy")
```
# Comparing Models
For many statistical models, including LMMs, it can be informative to
compare different models. Comparing different models can be used in
lots of different ways. Here are some examples, although they are not
meant to be exhaustive.
* Evaluate which of two (or more) models provides the best fit to the
data
* Evaluate / compare how the results for a particular predictor(s) of
interest change across two (or more) models
* Calculate the significance of multiple predictors
* Calculate effect sizes for one or multiple predictors
We will look at examples of the different uses of model comparisons in
this topic.
Just as there are many different kinds of models we can fit, even with
LMMs (e.g., with or without random slopes, etc.), so to there are many
different kinds and purposes for different model comparisons.
To begin with, let's imagine we are just trying to compare two models:
Model A and Model B. We can broadly classify the type of comparison
based on whether Model A and B are nested or non-nested models. We
will talk about what these mean next.
## Nested Models
Models are considered nested when one model is a restricted or
constrained version of another model. For example, suppose that
**Model A** predicts mood from stress and loneliness whereas
**Model B** predicts mood from loneliness only. Written as a formula,
these could be:
$$
ModelA: mood_{ij} = b_{0j} + b1 * loneliness_j + b2 * stress_{ij} + \varepsilon_{ij}
$$
and
$$
ModelB: mood_{ij} = b_{0j} + b1 * loneliness_j + 0 * stress_{ij} + \varepsilon_{ij}
$$
In **Model B**, I purposely used $0 * stress_{ij}$ to highlight that
when a predictor is left out of a model, it is the same as fixing
(sometimes also called constraining) the coefficient ($b2$ in this
case) to be exactly 0. In this case, we would say that
**Model B** is nested within **Model A**. In other words,
**Model A** contains every predictor and parameter in **Model B** plus
more.
Restricted
This idea is similar to the idea of nested data used in LMMs, but the
difference is that we are not talking about observations or
data, rather we are talking about the parameters of a model.
To summarize, briefly, we say that **Model B** is *nested* within
**Model A** if:
* setting one or more parameters in **Model A** to 0 yields the same
model as **Model B**.
* both models use the same data, have the same number of observations,
etc. Even if the same dataset is used in `R`, this does not
guarantee the same data are used because if additional predictors
are included in **Model A** and these extra predictors have some
missing data, by default `R` would drop the missing data and result
in a subset of cases being used for **Model A** than for **Model B**
If two models are nested, then we have the most options in terms of
comparing the two models. For example, we can evaluate whether
**Model A** is a statistically significantly better fit than is
**Model B** using a Likelihood Ratio Test (LRT)^[https://en.wikipedia.org/wiki/Likelihood-ratio_test].
We can compare the fit of each model and use the difference in fit to
derive effect sizes. We also can attribute any differences in the two
models to the parameter(s) that have been constrainted to 0 in **Model
B** from **Model A**.
A simple definition of the LRT test statistic, $\lambda$ is based on
two times the difference in the log likelihoods.
$$
\lambda = -2 * (LL_B - LL_A)
$$
You may wonder why the likelihood *ratio* test can be conducted by
taking the difference in the log likelihoods. It is because the log of
a ratio is the same as the difference in the logs of the numerator and
denominator.
$$
log_{e}\left(\frac{6}{2}\right) = log_{e}(6) - log_{e}(2)
$$
which we can confirm is true in `R`:
```{r}
## log of ratio
log(6/2)
## difference in logs
log(6) - log(2)
```
If the null hypothesis of no difference is true in the population,
then $\lambda$ will follow a chi-squared distribution with degrees of
freedom equal to the number of parameters constrained to 0 in **Model
B** from **Model A**, the difference in degrees of freedom used for
each model, that is:
$$
\lambda \sim \chi^2(DF_A - DF_B)
$$
Thus we often use a chi-square distribution in the LRT to look up the
p-value. Finally, note that because LRTs are based on the log
likelihoods (LL) from a model, we need true log likelihoods for the
LRT to be valid. Therefore, we cannot use restricted maximum
likelihood, we need to use maximum likelihood estimation.
### Nested Models in `R`
To see the idea of nested models and the LRT in action, let's examine
a concrete example in `R`. Here are two LMMs corresponding to
**Model A** and **Model B** formula we wrote previously. We can see in
the `R` code that the models are nested, the only difference is
`stress`. We set `REML = FALSE` to get maximum likelihood estimates so
that we get true log likelihoods. We also need to confirm that the two
models are based on the same number of observations. We can extract
just this information in `R` using the `nobs()` function. This lets
us confirm that the two models are fitted to the same data. For
example, if stress had some missing data that was not missing loneliness or
mood, it could be that **Model A** is based on fewer observations than
**Model B**.
```{r}
modela <- lmer(dMood ~ loneliness + dStress + (1 | ID), data = dm, REML = FALSE)
modelb <- lmer(dMood ~ loneliness + (1 | ID), data = dm, REML = FALSE)
nobs(modela)
nobs(modelb)
```
In this case, we can see that the number of observations are
identical. Now we can find the log likelihoods of both models by using
the `logLik()` function.
```{r}
logLik(modela)
logLik(modelb)
```
Now we have the log likelihoods (LL) of each model and the degrees of
freedom from both models. From this, we can calculate $\lambda$ and
then look up the p-value for a chi-square distribution with $\lambda$
and 1 degree of freedom (1 is the difference in degrees of freedom
between Model A and B). To get the p-value from a chi-square, we use
the `pchisq()` function.
```{r}
## lambda
-2 * (-432.79 - -396.81)
## p-value from a chi-square
pchisq(71.96, df = 1, lower.tail = FALSE)
```
In practice, we do not need to do these steps by hand, we can get a
test of two nested models in `R` using the `anova()` function (which
in this case is not actually analyzing the variance really). We use