-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy path02.Rmd
1098 lines (800 loc) · 55.9 KB
/
02.Rmd
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
# Fundamentals of Linear Regression
```{r, echo = FALSE, cache = FALSE}
options(width = 110)
```
> Linear regression is the foundation of most of the methods [Hayes described in his] book, so a solid understanding of the fundamentals of linear regression is essential. I assume that most readers have been exposed to linear regression in some form before discovering this book, and so some of the material will be review. Even so, I encourage everyone to read this chapter. [@hayesIntroductionMediationModeration2018, p. 30]
Since we're adding Bayes and the **tidyverse** into the mix, walking through this chapter will be double important, for us.
## Correlation and prediction
Here we load a couple necessary packages, load the data, and take a peek.
```{r, warning = F, message = F}
library(tidyverse)
glbwarm <- read_csv("data/glbwarm/glbwarm.csv")
glimpse(glbwarm)
```
If you are new to **tidyverse**-style syntax, possibly the oddest component is the pipe (i.e., `%>%`). I'm not going to explain the `%>%` in this project, but you might learn more about in [this brief clip](https://www.youtube.com/watch?v=9yjhxvu-pDg), starting around [minute 21:25 in this talk by Wickham](https://www.youtube.com/watch?v=K-ss_ag2k9E&t=1285s), or in [Section 5.6.1](https://r4ds.had.co.nz/transform.html#combining-multiple-operations-with-the-pipe) from @grolemundDataScience2017. Really, all of [Chapter 5](https://r4ds.had.co.nz/transform.html) of *R4DS* is great for new **R** and new **tidyverse** users, and [Chapter 3](https://r4ds.had.co.nz/data-visualisation.html) is a nice introduction to plotting with **ggplot2**.
Here is our version of Figure 2.1.
```{r, fig.width = 5, fig.height = 4}
glbwarm %>%
group_by(negemot, govact) %>%
count() %>%
ggplot(aes(x = negemot, y = govact, size = n)) +
geom_point(show.legend = F) +
labs(x = expression(paste("NEGEMOT: Negative emotions about climate change (", italic("X"), ")")),
y = expression(paste("GOVACT: Support for governmentaction (", italic("Y"), ")"))) +
theme_bw()
```
There are other ways to handle the [overplotting issue, such as jittering](https://ggplot2.tidyverse.org/reference/position_jitter.html).
```{r, fig.width = 5, fig.height = 4}
glbwarm %>%
ggplot(aes(x = negemot, y = govact)) +
geom_jitter(height = .05, width = .05,
alpha = 1/2, size = 1/3) +
labs(x = expression(paste("NEGEMOT: Negative emotions about climate change (", italic("X"), ")")),
y = expression(paste("GOVACT: Support for governmentaction (", italic("Y"), ")"))) +
theme_bw()
```
The `cor()` function is a simple way to compute a Pearson's correlation coefficient.
```{r}
cor(glbwarm$negemot, glbwarm$govact)
```
If you want more plentiful output, the `cor.test()` function returns a $t$-value, the degrees of freedom, the corresponding $p$-value and the 95% confidence intervals, in addition to the Pearson's correlation coefficient.
```{r}
cor.test(glbwarm$negemot, glbwarm$govact)
```
To get the Bayesian version, we'll open our focal statistical package, Bürkner's [**brms**](https://github.com/paul-buerkner/brms). I should briefly note that you could also do many of these analyses with other packages, such as [**blavaan**](https://cran.r-project.org/web/packages/blavaan/index.html) [@R-blavaan; @Merkle2018blavaan; @blavaan2021] or [**rstanarm**](https://github.com/stan-dev/rstanarm) [@R-rstanarm; @rstanarm2018]. I just prefer **brms**.
```{r, message = F, warning = F}
library(brms)
```
We'll start simple and just use the default priors and settings, but with the addition of parallel sampling via `cores = 4` and telling **brms** to save our output as an external file with the `file` argument.
```{r, model2.1}
model2.1 <- brm(
data = glbwarm,
family = gaussian,
bf(mvbind(negemot, govact) ~ 1) +
set_rescor(TRUE),
cores = 4,
file = "fits/model02.01")
```
We can examine a summary of the output with the `print()` function.
```{r}
print(model2.1)
```
In regression models, we typically use predictor variables to explain variation in our criterion variables. It's pretty much never the case that our predictors explain all the variation. The variation that's left over is often called residual variation, residual variance, residuals, error, or even $\epsilon$. Throughout the text, Hayes typically referred to it as $e$.
More formally, when we use likelihood-based estimators, such as maximum likelihood (popular with multilevel modeling and structural equation modeling) or Bayesian estimators, we express the models for our criterion variables in terms of likelihood functions. Probably the most common likelihood function, and the one consistent with the models in Hayes's text, is the Gaussian likelihood. With that likelihood we say our criterion variable $y_i$ is normally distributed with a mean $\mu$ and standard deviation $\sigma$. We might express that formally as
$$
y_i \sim \operatorname{Normal} (\mu, \sigma),
$$
where $\sim$ stands for "is distributed as" and $i$ indexes the $i$th row in the data. When we add predictors to the model, they are typically used to model the mean $\mu$. Thus, in the case there we have a sole predictor $x_i$ which varies across participants, we'd expand our model formula to
\begin{align*}
y_i & \sim \operatorname{Normal} (\mu_i, \sigma) \\
\mu_i & = \beta_0 + \beta_1 x_i,
\end{align*}
where $\beta_0$ is the intercept and $\beta_1$ is the slope for predictor $x$, which varies across cases. In this formulation, $\sigma$ is the standard deviation after accounting for the systemic variation explained by $x_i$. That is, it's the residual variance (i.e., $\epsilon$), but in a standard-deviation metric. *Why in a standard deviation metric?*, you ask. There are technical reasons why **brms** expresses it as a standard deviation which I'm not going to go into, here. Just beware that whereas many frequentist software packages express the residual variation in a variance metric, it's expressed in a standard-deviation metric in **brms**. Just go with it and move on.
Within the **brms** framework, $\sigma$ of the Gaussian likelihood is considered a "family-specific" parameter. As it turns out, there are many other fine likelihood functions in addition to the Gaussian and not all of them have a $\sigma$ parameter. For example, there is no $\sigma$ for the Poisson [^2.1] distribution, which is popular likelihood function for count variables. Because Bürkner made the **brms** package capable of a variety of likelihood functions, it behooved him to give this section of the output a generic name.
When you have a regression model with multiple Gaussian criterion variables, those variables will typically have some degree of covariation. It's often termed *residual covariance*, particularly within the structural equation modeling paradigm [^2.2]. But when you have an intercept-only regression model with multiple variables, that residual covariance is just a covariance. And when you express those variation parameters in terms of standard deviations $\sigma$, their covariance is expressed as a correlation $\rho$. Since our multivariate model is of two variables, we have one $\rho$ parameter for the $\sigma$'s, `rescor(negemot,govact)`, which is our Bayesian analogue to the Pearson's correlation.
To learn more about the multivariate syntax in **brms**, check out Bürkner's [-@Bürkner2022Multivariate] vignette, [*Estimating multivariate models with brms*](https://CRAN.R-project.org/package=brms/vignettes/brms_multivariate.html), or execute `vignette("brms_multivariate")`. Or just hold your horses until we get into mediation. All of our mediation models will use one version of the multivariate syntax or another.
But to clarify the output, above:
* 'Estimate' = the posterior mean, analogous to the frequentist point estimate,
* 'Est.Error' = the posterior $\textit{SD}$, analogous to the frequentist standard error,
* 'l-95% CI' = the lower-level of the percentile-based 95% Bayesian credible interval, and
* 'u-95% CI' = the upper-level of the same.
## The simple linear regression model
Here is how one might get the simple OLS coefficients in base **R** with the `lm()` function.
```{r}
(model2.2 <- lm(data = glbwarm, govact ~ 1 + negemot))
```
For more detailed output, put the model object `model2.2` into the `summary()` function.
```{r}
summary(model2.2)
```
Here's the Bayesian model in **brms**.
```{r model2.3}
model2.3 <- brm(
data = glbwarm,
family = gaussian,
govact ~ 1 + negemot,
cores = 4,
file = "fits/model02.03")
```
There are several ways to get a **brms** model summary. A go-to is with the `summary()` function, just like we did with our OLS version of the model.
```{r}
summary(model2.3)
```
The `print()` function works very much the same way. To get a more focused look, you can use the `posterior_summary()` function:
```{r}
posterior_summary(model2.3)
```
That also yields the log posterior, `lp__`, which you can learn more about in [this section](https://CRAN.R-project.org/package=rstan/vignettes/rstan.html#the-log-posterior-function-and-gradient) of the [-@standevelopmentteamRStanInterfaceStan2023] vignette by the Stan Development Team, [*RStan: the R interface to Stan*](https://cran.r-project.org/web/packages/rstan/vignettes/rstan.html) or [this brief blog post](https://www.jax.org/news-and-insights/jax-blog/2015/october/lp-in-stan-output) by Xulong Wang. We won't focus on the `lp__` directly in this project. But its influence will be lurking in the shadows.
The output also contains a row for `lprior`. This is the log of the joint prior and is a new addition to the output based on **brms** version 2.17.0. It is related to functionality from the up-and-coming [priorsense package](https://github.com/n-kall/priorsense) [@R-priorsense], which is based on new work, such as @kallioinen2021DetectingAndDiagnosing. Though you'll see it pop up in our output from time to time, the `lprior` will also be outside of our focus in this project.
But anyways, the `Q2.5` and `Q97.5`, are the lower- and upper-levels of the 95% credible intervals. The `Q` prefix stands for quantile ([see this thread](https://github.com/paul-buerkner/brms/issues/425)). In this case, these are a renamed version of the `l-95% CI` and `u-95% CI` columns from our `summary()` output.
To make a quick plot of the regression line, one can use the convenient `brms::conditional_effects()` function.
```{r, fig.width = 4.5, fig.height = 3}
conditional_effects(model2.3)
```
If you want to customize that output, you might nest it in `plot()`.
```{r, fig.width = 4.5, fig.height = 3}
plot(conditional_effects(model2.3),
points = T,
point_args = c(height = .05, width = .05,
alpha = 1/2, size = 1/3))
```
It's also useful to be able to work with the output of a **brms** model directly. For our first step, we'll put our posterior draws into a data frame with the `as_draws_df()` function.
```{r}
draws <- as_draws_df(model2.3)
head(draws)
```
Next, we'll use the `fitted()` function to compute model-implied summaries for the expected `govact` value, given particular predictor values. Our first model only has `negemot` as a predictor, and we'll ask for the expected `govact` values for `negemot` ranging from 0 to 7.
```{r}
nd <- tibble(negemot = seq(from = 0, to = 7, length.out = 30))
f <-
fitted(model2.3,
newdata = nd) %>%
data.frame() %>%
bind_cols(nd)
head(f)
```
The first two columns should look familiar to the output from `summary(model2.3)`, above. The next two columns, `Q2.5` and `Q97.5`, are the lower- and upper-levels of the 95% credible intervals, like we got from `posterior_summary()`. The final column is the result of the `bind_cols(nd)` code.
Here's our bespoke version of Figure 2.4.
```{r, fig.width = 5, fig.height = 4}
glbwarm %>%
group_by(negemot, govact) %>%
count() %>%
ggplot(aes(x = negemot)) +
geom_point(aes(y = govact, size = n),
show.legend = F) +
geom_ribbon(data = f,
aes(ymin = Q2.5, ymax = Q97.5),
fill = "grey75", alpha = 3/4) +
geom_line(data = f,
aes(y = Estimate)) +
annotate("text", x = 2.2, y = 7.5, label = "Cases with positive residuals", color = "red3") +
annotate("text", x = 4.75, y = .8, label = "Cases with negative residuals", color = "blue3") +
labs(x = expression("NEGEMOT: Negative emotions about climate change ("*italic("X")*")"),
y = expression("GOVACT: Support for governmentaction ("*italic("Y")*")")) +
coord_cartesian(xlim = range(glbwarm$negemot)) +
theme_bw()
```
Note how that figure is a combination of the original `glbwarm` data and our `f` output.
### Interpretation of the constant and regression coefficient.
"The regression constant is conceptually equivalent to the $Y$-intercept in the equation for a line. It quantifies the estimated value of $Y$ when $X = 0$" (p. 39).
### The standardized regression model.
> Thus far, the interpretation of the regression coefficients in a regression model has been couched in *unstandardized* or *raw metric* form. Many regression routines will also produce a version of the model in *standardized* form. The standardized regression model is what results when all variables are first standardized prior to estimation of the model by expressing each measurement in units of standard deviations from the sample mean. (p. 40, *emphasis* in the original)
**brms** will not produce standardized solutions on the fly. To get them, you will have to manually standardize the variables before entering them into the model.
### Simple linear regression with a dichotomous antecedent variable.
In the `glbwarm` data, `sex` is a dichotomous variable.
```{r, fig.width = 4, fig.height = 3}
glbwarm %>%
ggplot(aes(x = sex)) +
geom_bar() +
theme_bw()
```
In these data, `sex` is coded females = 0, males = 1. Here we add `sex` to the model.
```{r model2.4}
model2.4 <- brm(
data = glbwarm,
family = gaussian,
govact ~ 1 + sex,
cores = 4,
file = "fits/model02.04")
```
Check the summary.
```{r}
print(model2.4)
```
Our model summary is very close to that in the text. If you just wanted the coefficients, you might use the `fixef()` function.
```{r}
fixef(model2.4) %>% round(digits = 3)
```
Though not necessary, we used the `round()` function to reduce the number of significant digits in the output. You can get a little more information with the `posterior_summary()` function.
But since Bayesian estimation yields an entire posterior distribution, you can visualize that distribution in any number of ways. Because we'll be using **ggplot2**, we'll need to put the posterior draws into a data frame before plotting.
```{r}
draws <- as_draws_df(model2.4)
```
We could summarize the posterior with box plots
```{r, fig.width = 3, fig.height = 3, warning = F}
draws %>%
rename(female = b_Intercept) %>%
mutate(male = female + b_sex) %>%
pivot_longer(cols = c(male, female)) %>%
ggplot(aes(x = name, y = value)) +
geom_boxplot(aes(fill = name)) +
theme_bw() +
theme(legend.position = "none")
```
or with overlapping density plots
```{r, fig.width = 4, fig.height = 2, warning = F}
draws %>%
rename(female = b_Intercept) %>%
mutate(male = female + b_sex) %>%
pivot_longer(cols = c(male, female)) %>%
ggplot(aes(x = value, group = name, fill = name)) +
geom_density(color = "transparent", alpha = 3/4) +
theme_bw()
```
or even with violin plots with superimposed posterior medians and 95% intervals.
```{r, fig.width = 3, fig.height = 2.5, warning = F}
draws %>%
rename(female = b_Intercept) %>%
mutate(male = female + b_sex) %>%
pivot_longer(cols = c(male, female)) %>%
ggplot(aes(x = name, y = value)) +
geom_violin(aes(fill = name), color = "transparent", alpha = 3/4) +
stat_summary(fun = median,
fun.min = function(i){quantile(i, probs = .025)},
fun.max = function(i){quantile(i, probs = .975)}) +
theme_bw() +
theme(legend.position = "none")
```
For even more ideas, see [Matthew Kay](https://twitter.com/mjskay)'s [**tidybayes**](https://mjskay.github.io/tidybayes/articles/tidybayes.html) and [**ggdist**](https://mjskay.github.io/ggdist/) packages.
As Hayes discussed on page 42, you can also get a sense of the model estimates for women and men with a little addition. Here we continue to use the `round()` function to simplify the output.
```{r}
# for women
round(fixef(model2.4)[1, ], digits = 2)
# for men (just the posterior mean)
round(fixef(model2.4)[1, 1] + fixef(model2.4)[2, 1], digits = 2)
```
Hayes then considered that
> although the model will always generate the group means, the regression coefficient and regression constant will depend on how the two groups are coded. For instance, suppose females were coded $X = −1$ and males were coded $X = 1$. (p. 42)
To follow along, we'll first recode `sex`, saving it as `sex_recode`.
```{r}
glbwarm <- glbwarm %>%
mutate(sex_recode = if_else(sex == 0, -1, 1))
```
Now fit the new model.
```{r model2.5}
model2.5 <- brm(
data = glbwarm,
family = gaussian,
govact ~ 1 + sex_recode,
cores = 4,
file = "fits/model02.05")
```
Check the primary coefficients.
```{r}
fixef(model2.5)
```
They match up well with the results in the middle of page 42. Now the `Intercept` in the output, what Hayes called $i_Y$ is the unweighted mean of the means, $\big (\overline Y_\text{male} + \overline Y_\text{female} \big ) \big / 2$, and the coefficient `sex_recode` (i.e., what Hayes called $b$) is one half the difference between those means. Here's how to work with the posterior draws from this model to reproduce the group mean estimates.
```{r, fig.width = 4, fig.height = 2, warning = F}
as_draws_df(model2.5) %>%
transmute(male = b_Intercept + b_sex_recode * 1,
female = b_Intercept + b_sex_recode * -1) %>%
pivot_longer(everything()) %>%
ggplot(aes(x = value, group = name, fill = name)) +
geom_density(color = "transparent", alpha = 3/4) +
theme_bw()
```
You'll see it looks just like the plot from above.
#### A caution about the standardized regression coefficient.
> The standardized regression coefficient is a function of both the mean difference and the distribution of the cases across the groups. This is an undesirable property of $\tilde b$ when $X$ is dichotomous. *I recommend that the standardized regression coefficient for a dichotomous antecedent variable not be interpreted or reported*.
>
> If you desire an index of a mean difference in standard deviation units, I recommend standardizing $Y$ but not the dichotomous $X$ and then interpreting the *unstandardized* regression coefficient in a model estimating $Z_Y$ from $X$. In such a model, $b$ is a *partially* standardized regression coefficient. (pp. 43--44, *emphasis* in the original)
Here's how to fit the partially-standardized model, first with `lm()`.
```{r, message = F, warning = F}
# standardize the Y
glbwarm <- glbwarm %>%
mutate(govact_z = (govact - mean(govact)) / sd(govact))
# fit the model
lm(data = glbwarm, govact_z ~ 1 + sex) %>%
# summarize
summary()
```
Now we'll fit the model as Bayesians with `brms::brm()`.
```{r model2.6}
model2.6 <- brm(
data = glbwarm,
family = gaussian,
govact_z ~ 1 + sex,
cores = 4,
file = "fits/model02.06")
```
```{r}
fixef(model2.6)
```
> The constant $i_Y$ is the mean standardized $Y$ for females, and $b$ is the mean difference between males and females in standard deviations of $Y$. So men are estimated to differ from women by 0.197 standard deviations in their support for government action. The negative sign for $b$ means males are lower than females, on average, in their support. (p. 44)
The parameter summaries from our Bayesian model was the same as the OLS summaries up to two decimal places. This will often be the case. One is not more correct. They are the results of using different procedures.
### A note on symbolic representations.
This section is worthy of repeating in full.
> A brief digression is in order at this point. It is important when reporting the results of an analysis to define the symbols you use unless there is a strong convention, for a failure to do so can invite confusion. Different uses of $b$ and $\beta$ in regression analysis are an important case in point. There is much inconsistency in the substantive and methodology literature as to how regression coefficients are symbolized in unstandardized versus standardized form. Some use $b$ or $B$ to refer to the unstandardized regression coefficient and $\beta$ to refer to the standardized regression coefficient. Others, rather than using $\beta$, spell it out by referencing "beta weights" or just talk about the "betas." Some use $\beta$ to refer to a population regression coefficient, to distinguish it from a sample estimate, others use $\beta$ as the unstandardized regression weight, and there are still others who use $\hat \beta$ to refer to a sample unstandardized regression coefficient and leave the hat off for its corresponding population or "true" value. In this book, I use $\tilde \beta$ for the standardized regression weight.
>
Ultimately, the symbols we use are for the most part arbitrary. We can use any symbols we want. My point is that you should not assume others will know what symbols you use mean, for your familiar symbols to represent certain concepts may not be understood as representing those concepts by all. The same applies to terms such as "beta coefficient" or other verbalizations of symbols. Best to define your symbols in advance, or otherwise let your reader know what your symbols mean when used in text and tables. This will help others better understand and interpret your work. (p. 44)
## Alternative explanations for association
> That "correlation does not imply causation" is etched into the brains of all scientists. If variables $X$ and $Y$ are correlated, that doesn’t mean that $X$ causes $Y$ or that $Y$ causes $X$. The ability to infer cause--effect is not even a statistical matter in the end. Rather, it is the design of one's study, the data collection procedures one employs, and theoretical plausibility that most directly influence whether a cause--effect claim can be made and with what degree of confidence, not the size or sign of a statistical index of association. (p. 45)
On page 46, Hayes reported a couple correlations. Here's how to get them from base **R**.
```{r}
cor(glbwarm$sex, glbwarm$negemot)
cor(glbwarm$sex, glbwarm$govact)
```
Again, if we wanted to get full Bayesian estimates, we'd fit an intercept-only multivariate model.
```{r model2.7}
model2.7 <- brm(
data = glbwarm,
family = gaussian,
bf(mvbind(negemot, govact, sex) ~ 1) +
set_rescor(TRUE),
cores = 4,
file = "fits/model02.07")
```
```{r}
print(model2.7, digits = 3)
```
For our purposes, the action is in the 'rescor($i$, $j$)' portions of the 'Family Specific Parameters' section.
Anyway, if you wanted to get all the Pearson's correlations among the `glbwarm` variables, rather than piecewise `cor()` approach, you could use the `lowerCor()` function from the [**psych** package](https://cran.r-project.org/web/packages/psych/index.html) [@R-psych].
```{r}
psych::lowerCor(glbwarm[, 1:7], digits = 3)
```
## Multiple linear regression
> The simple linear regression model is easily extended to the estimation of a consequent variable using more than one antecedent variable. Including more than one antecedent in a regression model allows you to simultaneously investigate the role of multiple influences on a consequent variable. An additional and important benefit of the multiple regression model is that it provides various measures of *partial association* that quantify the component of the association between an antecedent and a consequent that is unique to that antecedent relative to other antecedent variables in the model. (p. 48, *emphasis* in the original)
Using Hayes' notation, the linear model we're about to fit follows the basic equation
$$\hat Y = i_Y + b_1 X_1 + b_2 X_2 + b_3 X_3 + b_4 X_4 + b_4 X_5.$$
For us, there's technically more involved because our Bayesian paradigm includes priors, which we're not focusing on at the moment. Anyway, there's nothing particularly special about jumping from univariable to multivariable models with **brms**. You just keep tacking on predictors with the `+` operator.
```{r model2.8}
model2.8 <- brm(
data = glbwarm,
family = gaussian,
govact ~ 1 + negemot + posemot + ideology + sex + age,
cores = 4,
file = "fits/model02.08")
```
```{r}
print(model2.8)
```
Following Hayes on pages 50 and 51, here is the posterior mean (i.e., what you might call the Bayesian point estimate) for someone with
* negative emotions = 3,
* positive emotions = 4,
* `ideology` = 2,
* is male (i.e., `sex` = 1), and
* is 30 years of `age`.
```{r}
fixef(model2.8)[1] +
fixef(model2.8)[2] * 3 +
fixef(model2.8)[3] * 4 +
fixef(model2.8)[4] * 2 +
fixef(model2.8)[5] * 1 +
fixef(model2.8)[6] * 30
```
Here's the same deal for a man of the same profile, but with one point higher on `negemot`.
```{r}
fixef(model2.8)[1] +
fixef(model2.8)[2] * 4 +
fixef(model2.8)[3] * 4 +
fixef(model2.8)[4] * 2 +
fixef(model2.8)[5] * 1 +
fixef(model2.8)[6] * 30
```
If you want a full expression of the model uncertainty in terms of the shape of the posterior distribution and the 95% intervals, you'll might use `as_draws_df()` and do a little data processing.
```{r, fig.width = 4, fig.height = 3, warning = F, message = F}
draws <- as_draws_df(model2.8)
draws <- draws %>%
mutate(our_posterior = b_Intercept + b_negemot * 4 + b_posemot * 4 + b_ideology * 2 + b_sex * 1 + b_age * 30)
# this intermediary step will make it easier to specify the break points and their labels for the x-axis
post_summary <-
quantile(draws$our_posterior, probs = c(.025, .5, .975)) %>%
as_tibble() %>%
mutate(labels = value %>%
round(digits = 3) %>%
as.character())
# plot!
ggplot(data = draws,
aes(x = our_posterior)) +
geom_density(fill = "black") +
geom_vline(xintercept = post_summary$value,
size = c(.5, .75, .5), linetype = c(2, 1, 2), color = "white") +
scale_x_continuous(NULL,
breaks = post_summary$value,
labels = post_summary$labels) +
scale_y_continuous(NULL, breaks = NULL) +
labs(subtitle = "The expected govact score for a 30-year-old man for\nwhom negemot and posemot both equal 4 and ideology\nequals 2. The solid and dashed white vertical lines are the\nposterior median and 95% intervals, respectively.") +
theme_bw()
```
In the text, Hayes showed that individuals based on these two profiles would be expected to differ by 0.441 (i.e., $5.244 - 4.803 = 0.441$). That's fine if you're only working with OLS point estimates. But a proper Bayesian approach would express the difference in terms of an entire poster distribution, or at least a point estimate accompanied by some sort of intervals. Here we'll just work with the posterior to create a difference distribution. You could do that with a little deft `as_draws_df()` wrangling. Here we'll employ `fitted()`.
```{r, fig.width = 4, fig.height = 3}
nd <- tibble(
negemot = c(3, 4),
posemot = 4,
ideology = 2,
sex = 1,
age = 30)
fitted(model2.8,
newdata = nd,
summary = F) %>%
data.frame() %>%
set_names(str_c("condition_", letters[1:2])) %>%
mutate(difference = condition_b - condition_a) %>%
ggplot(aes(x = difference)) +
geom_density(fill = "black", color = "transparent") +
scale_y_continuous(NULL, breaks = NULL) +
ggtitle("The posterior density for the difference between\nthe two conditions.") +
theme_bw()
```
### The standardized regression model.
As **brms** doesn't automatically give us the standardized coefficients the way OLS output often does, we'll have to be proactive. One solution is just to standardized the data themselves and then re-fit the model with those standardized variables. That leads us to the issue of how one standardized variables to begin with. Recall that standardizing entails subtracting the mean of a variable from that variable and then dividing that value by the standard deviation. We don't want to do that by hand. So one handy way is to make a custom function to do that work for us.
```{r}
standardize <- function(x) {
(x - mean(x)) / sd(x)
}
```
To learn more about making custom functions in **R**, check our [Chapter 19](https://r4ds.had.co.nz/functions.html) of *R4DS*.
Here we'll employ our custom `standardize()` function to make standardized versions of our variables.
```{r}
glbwarm <- glbwarm %>%
mutate(posemot_z = standardize(posemot),
negemot_z = standardize(negemot),
ideology_z = standardize(ideology),
sex_z = standardize(sex),
age_z = standardize(age))
```
Now we've got us our standardized variables, let's fit a standardized model.
```{r model2.9}
model2.9 <- brm(
data = glbwarm,
family = gaussian,
govact_z ~ 1 + negemot_z + posemot_z + ideology_z + sex_z + age_z,
cores = 4,
file = "fits/model02.09")
```
Here are the newly standardized coefficient summaries, minus the `Intercept`.
```{r}
fixef(model2.9)[-1, ] %>% round(3)
```
Our coefficients match up nicely with those on page 52 in the text. Just as with Hayes's OLS estimates, we should not attempt to interpret the standardized `sex_z` coefficient from our Bayesian model.
Here's how we'd fit a *partially*-standardized model--a model in which all variables except for `sex` are standardized.
```{r model2.10, warning = F, message = F}
model2.10 <- update(
model2.9,
newdata = glbwarm,
formula = govact_z ~ 1 + negemot_z + posemot_z + ideology_z + sex + age_z,
cores = 4,
file = "fits/model02.10")
```
Notice our use of the `update()` function. If you want to hastily fit a **brms** model of the same basic form of a prior model, you can just update some of the parameters of that original fit. In this case, all we did was swap out one of the predictors. To accommodate that change, we used the `newdata` argument so the model had access to the new variable and then we fed in the new formula.
Anyway, here are the coefficient summaries, including the `Intercept`, for the partially-standardized model.
```{r}
fixef(model2.10) %>% round(digits = 3)
```
As Hayes wrote, now `sex` = `r round(brms::fixef(model2.10)[5], digits = 3)` has a sensible interpretation. "We can say that men and women differ by [`r round(brms::fixef(model2.10)[5], digits = 3)`] standard deviations in their support for government action when all other variables in the model are held constant (p. 53)."
On page 54, Hayes gave us the equation to transform unstandardized coefficients to standardized ones:
$$
\tilde b_i = b_i \left ( \frac{\textit{SD}_{X_{i}}}{\textit{SD}_{Y}} \right )
$$
Let's give it a whirl with `negemot`.
```{r}
# here's the coefficient for `negemot` from the standardized model, `model2.9`
fixef(model2.9)["negemot_z", "Estimate"]
# here's the coefficient for `negemot` from the unstandardized model, `model2.8`
fixef(model2.8)["negemot", "Estimate"]
# and here we use Hayes' formula to standardize the unstandardized coefficient
fixef(model2.8)["negemot", "Estimate"] * (sd(glbwarm$negemot) / sd(glbwarm$govact))
```
Looks like we got it within rounding error--pretty good! However, that was just the posterior mean, the Bayesian point estimate. If we want to more fully express the uncertainty around the mean--and we do--, we'll need to work with the posterior draws.
```{r, warning = F}
# the posterior draws from the unstandardized model
as_draws_df(model2.8) %>%
# using Hayes' formula to standardize `b_negemot`
mutate(`hand-made b_negemot_z` = b_negemot * (sd(glbwarm$negemot) / sd(glbwarm$govact))) %>%
# tacking on the `b_negemot_z` column from the standardized `model2.9` models posterior draws
bind_cols(as_draws_df(model2.9) %>%
select(b_negemot_z)) %>%
# converting the data to the long format and grouping by `name`
pivot_longer(c(`hand-made b_negemot_z`, b_negemot_z)) %>%
group_by(name) %>%
# here we summarize the results
summarise(mean = mean(value),
sd = sd(value),
ll = quantile(value, probs = .025),
ul = quantile(value, probs = .975)) %>%
mutate_if(is.double, round, digits = 3)
```
Our summary confirms that we can apply Hayes's formula to a `as_draws_df()` column in order to get fuller summary statistics for a hand-converted standardized coefficient. This would be in full compliance with, say, APA recommendations to include 95% intervals with all effect sizes--the standardized regression coefficient being the effect size, here.
## Measures of model fit
In the Bayesian world, we don't tend to appeal to the $\textit{SS}_{residual}$, the $\textit{MS}_{residual}$, or the standard error of estimate. We do sometimes, however, appeal to the $R^2$. I'm not going to go into the technical details here, but you should be aware that the Bayesian $R^2$ is not calculated the same as the OLS $R^2$. If you want to dive in, check out the [-@gelmanRsquaredBayesianRegression2019] paper by Gelman, Goodrich, Gabry, and Vehtari, [*R-squared for Bayesian regression models*](https://doi.org/10.1080/00031305.2018.1549100). Here's how to get it with **brms**.
```{r}
bayes_R2(model2.8, summary = T) %>% round(digits = 3)
```
It even comes with 95% intervals, which will make the editors at APA journals happy. If you want to go beyond summary statistics and take a look at the full posterior, just set `summary = F` and data wrangle and plot as usual.
```{r, fig.width = 6, fig.height = 1.75}
bayes_R2(model2.8, summary = F) %>%
data.frame() %>%
ggplot(aes(x = R2)) +
geom_density(fill = "black", color = "transparent") +
scale_y_continuous(NULL, breaks = NULL) +
labs(title = expression(paste("Behold: The Bayesian ", italic("R")^{2}, " distribution for model 2.8")),
x = NULL) +
coord_cartesian(xlim = 0:1) +
theme_bw()
```
Another way we examine model fit is with graphical posterior predictive checks. Posterior predictive checking is a very general approach, which you might learn more about from @gabry2019visualization or with a few keyword searches in on [Gelman's blog](https://statmodeling.stat.columbia.edu/). One basic way is to use the model in order to simulate data and then compare those data with the original data--the basic idea being that good fitting models should produce data similar to the original data.
Recall how we've used `fitted()` to make regression lines and expected values? We'll, now we'll use `predict()` to simulate data based on our models.
```{r, fig.width = 10, fig.height = 3, warning = F, message = F}
set.seed(2)
predict(model2.8,
summary = F,
nsamples = 3) %>%
data.frame() %>%
set_names(1:nrow(glbwarm)) %>%
mutate(simulation = str_c("simulation: ", 1:n())) %>%
pivot_longer(-simulation,
names_to = "row",
values_to = "govact") %>%
bind_cols(
bind_rows(
glbwarm %>% select(-govact),
glbwarm %>% select(-govact),
glbwarm %>% select(-govact))
) %>%
ggplot(aes(x = negemot, y = govact)) +
geom_jitter(height = .05, width = .05,
alpha = 1/2, size = 1/3) +
coord_cartesian(ylim = c(0, 9)) +
theme_bw() +
facet_wrap(~ simulation, ncol = 3)
```
The question is, do these simulated data sets look like the original data? Let's see.
```{r, fig.width = 3.35, fig.height = 2.7}
glbwarm %>%
ggplot(aes(x = negemot, y = govact)) +
geom_jitter(height = .05, width = .05,
alpha = 1/2, size = 1/3) +
coord_cartesian(ylim = c(0, 9)) +
theme_bw()
```
Overall, the simulations aren't bad. But in all three `govact` tends to veer above 7.5, which is where the original data appear to be bounded. But otherwise the overall shape is pretty close, at least with respect to `negemot`.
There's nothing special about three simulations. Three is just more than one and gives you a sense of the variance across simulations. Also, we only examined the model fit with respect to `negemot`. Since there are other variables in the model, we might also assess the model based on them.
Another method is with the `brms::pp_check()` function, which allows users to access a variety of convenience functions from the [**bayesplot** package](https://CRAN.R-project.org/package=bayesplot) [@R-bayesplot; @gabry2019visualization]. Here we'll use the default settings and just tack on `theme_bw()` for aesthetics.
```{r, fig.width = 6, fig.height = 3, message = F}
set.seed(2)
pp_check(model2.8) +
theme_bw()
```
What we did was simulate 10 data sets worth of `govact` values, plot their densities (i.e., the thin blue lines) and compare them with the density of the original `govact` values. What we want is for the thin blue lines to largely align with the thick blue line. Though not perfect, the simulations from our `model2.8` did a pretty okay job of reproducing the original `govact` distribution. For more ideas on this method, see the [**brms** reference manual](https://CRAN.R-project.org/package=brms/brms.pdf) [@brms2022RM] and Gabry's [-@gabryGraphicalPosteriorPredictive2019] vignette, [*Graphical posterior predictive checks using the bayesplot package*](https://CRAN.R-project.org/package=bayesplot/vignettes/graphical-ppcs.html).
## Statistical inference
Here's a **tidyverse** way to do Hayes' simulation from page 57. We're just using OLS regression with the `lm()` function. You could do this with Bayesian HMC estimation, but man would it take a while. For our first step, we'll define two custom functions.
```{r}
# this first one will use the `slice_sample()` function to randomly sample from `glbwarm`
make_sample <- function(i) {
set.seed(i)
slice_sample(glbwarm, n = 50, replace = F)
}
# this second function will fit our model, the same one from `model2.8`, to each of our subsamples
glbwarm_model <- function(df) {
lm(govact ~ 1 + negemot + posemot + ideology + sex + age, data = df)
}
```
Now we'll run the simulation, wrangle the output, and plot in one fell swoop.
```{r sim, cache = T, fig.width = 6, fig.height = 3.5}
# we need an iteration index, which will double as the values we set our seed with in our `make_sample()` function
tibble(iter = 1:1e4) %>%
group_by(iter) %>%
# inserting our subsamples
mutate(sample = map(iter, make_sample)) %>%
# fitting our models
mutate(model = map(sample, glbwarm_model)) %>%
# taking those model results and tidying them with the broom package
mutate(broom = map(model, broom::tidy)) %>%
# unnesting allows us to access our model results
unnest(broom) %>%
# we're only going to focus on the estimates for `negemot`
filter(term == "negemot") %>%
# here it is, Figure 2.7
ggplot(aes(x = estimate)) +
geom_histogram(binwidth = .025, boundary = 0) +
labs(x = "Unstandardized regression coefficient for negemot",
y = "Frequency in 1e4 samples of size 50") +
theme_bw()
```
To learn more about this approach to simulations, see [Section 25.2.1](https://r4ds.had.co.nz/many-models.html#nested-data) in *R4DS*.
### Testing a null hypothesis.
As Bayesians, we don't need to wed ourselves to the null hypothesis. We're not interested in the probability of the data given the null hypothesis. Bayes' rule,
$$p(\theta | D) = \frac{p(D | \theta) p(\theta)}{p(D)},$$
gives us the probability of the model parameters, given the data. Though I acknowledge [different researchers might set out to ask different things of their data](http://daniellakens.blogspot.com/2017/11/the-statisticians-fallacy.html), I propose we're generally more interested in determining the most probable parameter values than we are the probabilities tested within the NHST paradigm.
```{r, fig.width = 6, fig.height = 3.5}
as_draws_df(model2.8) %>%
ggplot(aes(x = b_negemot)) +
geom_density(fill = "black", color = "transparent") +
geom_vline(xintercept = posterior_interval(model2.8)["b_negemot", ],
color = "white", linetype = 2) +
scale_x_continuous(breaks = posterior_interval(model2.8)["b_negemot", ] %>% as.double(),
labels = posterior_interval(model2.8)["b_negemot", ] %>% as.double() %>% round(digits = 2) %>% as.character()) +
scale_y_continuous(NULL, breaks = NULL) +
labs(subtitle = "The most probable values for our b_negemot parameter are the ones around the peak\nof the density. For convenience, the dashed lines denote the 95% credible intervals.\nSure, you could ask yourself, 'Is zero within those intervals?' But with such rich output,\nthat seems like an impoverished question to ask.") +
theme_bw()
```
For more discussion the NHST paradigm and how it compares with variations of Bayesian statistics, check out Kruschke's [-@kruschkeDoingBayesianData2015] text, [*Doing Bayesian data analysis: A tutorial with R, JAGS, and Stan*](https://sites.google.com/site/doingbayesiandataanalysis/), particularly chapters 10 through 13. You can find my [-@kurzDoingBayesianDataAnalysis2023] **brms** + **tidyverse** translation of that text at [https://bookdown.org/content/3686/](https://bookdown.org/content/3686/).
### Interval estimation.
Within the Bayesian paradigm, we don't use 95% intervals based on the typical frequentist formula. With the **brms** package, we typically use percentile-based intervals. Take the 95% credible intervals for the `negemot` coefficient from model `model2.8`:
```{r}
posterior_interval(model2.8)["b_negemot", ]
```
We can actually get those intervals with the simple use of the base **R** `quantile()` function.
```{r}
as_draws_df(model2.8) %>%
summarize(the_2.5_percentile = quantile(b_negemot, probs = .025),
the_97.5_percentile = quantile(b_negemot, probs = .975))
```
The consequence of this is that our Bayesian credible intervals aren't necessarily symmetric, which is fine because the posterior distribution for a given parameter isn't always symmetric. But not all Bayesian intervals are percentile based. John Kruschke, for example, often recommends highest posterior density intervals in [his work](https://sites.google.com/site/doingbayesiandataanalysis/). The **brms** package doesn't have a convenience function for these, but you can compute them with help from the [**HDInterval** package](https://CRAN.R-project.org/package=HDInterval) [@R-HDInterval].
```{r, warning = F}
library(HDInterval)
hdi(as_draws_df(model2.8)[ , "b_negemot"], credMass = .95)
```
Finally, because Bayesians aren't bound to the NHST paradigm, we aren't bound to 95% intervals, either. For example, in both his excellent [-@mcelreathStatisticalRethinkingBayesian2020] [text](http://xcelab.net/rm/statistical-rethinking/) and as a default in its accompanying [**rethinking** package](https://github.com/rmcelreath/rethinking), [Richard McElreath](https://twitter.com/rlmcelreath) often uses 89% intervals. Alternatively, [Andrew Gelman](https://twitter.com/StatModeling) has publicly advocated for [50% intervals](http://andrewgelman.com/2016/11/05/why-i-prefer-50-to-95-intervals/). The most important thing is to express the uncertainty in the posterior in a clearly-specified way. If you'd like, say, 80% intervals in your model summary, you can insert a `prob` argument into either `print()` or `summary()`.
```{r}
print(model2.8, prob = .8)
```
Note how the names of two of our columns changed to 'l-80% CI' and 'u-80% CI'.
You can specify custom percentile levels with `posterior_summary()`:
```{r}
posterior_summary(model2.8, probs = c(.9, .8, .7, .6, .4, .3, .2, .1)) %>%
round(digits = 2)
```
And of course, you can use multiple interval summaries when you `summarize()` the output from `as_draws_df()`. E.g.,
```{r, warning = F}
as_draws_df(model2.8) %>%
pivot_longer(b_Intercept:b_age) %>%
group_by(name) %>%
summarize(`l-95% CI` = quantile(value, probs = .025),
`u-95% CI` = quantile(value, probs = .975),
`l-50% CI` = quantile(value, probs = .25),
`u-50% CI` = quantile(value, probs = .75)) %>%
mutate_if(is.double, round, digits = 2)
```
Throughout this project, I'm going to be lazy and default to conventional 95% intervals, with occasional appearances of 50% intervals.
### Testing a hypothesis about a set of antecedent variables.
Here we'll make use of the `update()` function to hastily fit our reduced model, `model2.11`.
```{r model2.11}
model2.11 <- update(
model2.8,
govact ~ 1 + ideology + sex + age,
cores = 4,
file = "fits/model02.11")
```
We can get a look at the $R^2$ summaries for our competing models like this.
```{r}
bayes_R2(model2.8) %>% round(digits = 2)
bayes_R2(model2.11) %>% round(digits = 2)
```
So far it looks like our fuller model, `model2.8`, accounts for more variation in the data. If we wanted to look at their distributions, we'd set `summary = F` in the `bayes_R2()` function and convert the results to a data frame. Here we use `bind_cols()` to put the $R^2$ results for both in the same tibble.
```{r}
r2 <- cbind(bayes_R2(model2.8, summary = F),
bayes_R2(model2.11, summary = F)) %>%
data.frame() %>%
set_names(str_c("model2.", c(8, 11)))
head(r2)
```
With our `r2` tibble in hand, we're ready to plot.
```{r, fig.width = 6, fig.height = 1.5}
r2 %>%
pivot_longer(everything()) %>%
ggplot(aes(x = value, fill = name)) +
geom_density(color = "transparent") +
scale_y_continuous(NULL, breaks = NULL) +
xlab(expression(paste(italic(R)^2))) +
coord_cartesian(xlim = 0:1) +
theme_bw()
```
Yep, the $R^2$ distribution for `model2.8`, the one including the emotion variables, is clearly larger than that for the more parsimonious `model2.11`. And it'd just take a little more data wrangling to get a formal $R^2$ difference score.
```{r, fig.width = 6, fig.height = 2}
r2 %>%
mutate(difference = model2.11 - model2.8) %>%
ggplot(aes(x = difference)) +
geom_density(fill = "black", color = "transparent") +
scale_y_continuous(NULL, breaks = NULL) +
coord_cartesian(xlim = -1:0) +
labs(title = expression(paste(Delta, italic(R)^{2})),
subtitle = expression(paste("This is the amount the ", italic(R)^{2}, " dropped after pruning the emotion variables from the model.")),
x = NULL) +
theme_bw()
```
The $R^2$ approach is popular within the social sciences. But it has its limitations, the first of which is that it doesn't correct for model complexity. The second is it's not applicable to a range of models, such as those that do not use the Gaussian likelihood (e.g., logistic regression) or to multilevel models.
Happily, information criteria offer a more general framework. The AIC is the most popular information criteria among frequentists. Within the Bayesian world, we have the DIC, the WAIC, and the LOO. The DIC is quickly falling out of favor and is not immediately available with the **brms** package. However, we can use the WAIC and the LOO, both of which are computed in **brms** via the [**loo** package](https://github.com/stan-dev/loo) [@R-loo; @vehtariPracticalBayesianModel2017; @yaoUsingStackingAverage2018].
With **brms**, you can compute the WAIC or LOO values and add them to your model fit object with the `add_criterion()` function.
```{r, message = F, warning = F}
model2.8 <- add_criterion(model2.8, criterion = c("loo", "waic"))
model2.11 <- add_criterion(model2.11, criterion = c("loo", "waic"))
```
Here's the main loo-summary output for `model4`.
```{r}
model2.8$criteria$loo
```
You get a wealth of output, more of which can be seen with `str(model2.8$loo)`. For now, notice the "All Pareto k estimates are good (k < 0.5)." For technical details on Pareto $k$ values, see @vehtariUsingLooPackage2020. In short, each case in the data gets its own $k$ value and we like it when those $k$'s are low. The makers of the **loo** package get worried when those $k$'s exceed 0.7 and as a result you will get a warning message when they do. Happily, we have no such warning messages in this example.
If you want to work with the $k$ values directly, you can extract them and place them into a data frame like so.
```{r}
model2.8$criteria$loo$diagnostics %>%
data.frame() %>%
head()
```
The `pareto_k` values can be used to examine cases that are overly-influential on the model parameters, something akin to a Cook's $D_i$. See, for example [this discussion on stackoverflow.com](https://stackoverflow.com/questions/39578834/linear-model-diagnostics-for-bayesian-models-using-rstan/39595436) in which several members of the [Stan team](http://mc-stan.org) weighed in. The issue is also discussed in @vehtariPracticalBayesianModel2017 and in [this lecture by Aki Vehtari](https://www.youtube.com/watch?v=FUROJM3u5HQ&feature=youtu.be&a=).
But anyway, we're getting ahead of ourselves. Back to the LOO.
Like other information criteria, the LOO values aren't of interest in and of themselves. However, the values of one model's LOO relative to that of another is of great interest. We generally prefer models with lower information criteria. With `compare_ic()`, we can compute a formal difference score between multiple loo objects.
```{r}
loo_compare(model2.8, model2.11) %>% print(simplify = F)
```
We also get a standard error. Here it looks like `model2.8` was substantially better, in terms of LOO-values, than `model2.11`.
For more on the LOO, see the [**loo** reference manual](https://cran.r-project.org/web/packages/loo/loo.pdf) [@loo2022RM], Vehtari and Gabry's handy [-@vehtariUsingLooPackage2020] [vignette](https://CRAN.R-project.org/package=loo/vignettes/loo2-example.html), or the scholarly papers referenced therein. Also, McElreath discussed the LOO in the second [-@mcelreathStatisticalRethinkingBayesian2020] version of his text, as well as in this [lecture](https://www.youtube.com/watch?v=gjrsYDJbRh0&list=PLDcUM9US4XdNM4Edgs7weiyIguLSToZRI&index=8).
## Multicategorical antecedent variables
"To include a multicategorical antecedent variable representing $g$ groups in a regression model, it must be represented with $g − 1$ variables using one of a variety of different group coding systems". (p. 66) This isn't strictly true, but we digress. Hayes went on...
> One popular system for coding groups is *indicator coding*, also known as *dummy coding*. With indicator coding, $g − 1$ *indicator variables* containing either a zero or one represent which of the $g$ groups a case belongs in, and these indicator variables are used as antecedents in a regression model. To construct indicator codes, create $g − 1$ variables $D_i$ for each case set to 1 if the case is in group $i$, otherwise set $D_i$ to zero. (p. 66, *emphasis* in the original)
Before we get to that, we should examine our multicategorical antecedent variable, `partyid`. It's coded 1 = Democrat 2 = Independent 3 = Republican. You can get a count of the cases within a give `partyid` like this.
```{r}
glbwarm %>%
count(partyid)
```
We can get grouped means for `govact` like this.
```{r, message = F}
glbwarm %>%
group_by(partyid) %>%
summarize(mean_support_for_governmental_action = mean(govact))
```
We can make dummies with the `if_else()` function. Here we'll just go ahead and do that right within the `brm()` function.
```{r model2.12}
model2.12 <- brm(
data = glbwarm %>%
mutate(Democrat = if_else(partyid == 1, 1, 0),
Republican = if_else(partyid == 3, 1, 0)),
family = gaussian,
govact ~ 1 + Democrat + Republican,
chains = 4, cores = 4,
file = "fits/model02.12")
```