-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy path50-rm_ANOVA.Rmd
360 lines (267 loc) · 12 KB
/
50-rm_ANOVA.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
# Repeated Measures ANOVA
Required Packages
```{r}
library(tidyverse) # Loads several very helpful 'tidy' packages
library(furniture) # Nice tables (by our own Tyson Barrett)
library(afex) # needed for ANOVA, emmeans is loaded automatically.
library(multcomp) # for advanced control for multiple testing/Type 1 error
```
## Tutorial - Fitting RM ANOVA Models with afex::aov_4()
The `aov_4()` function from the `afex` package fits ANOVA models (oneway, two-way, repeated measures, and mixed design). It needs at least two arguments:
1. formula: `continuous_var ~ 1 + (RM_var|id_var)` *one observation per subject for each level of the `RMvar`, so each `id_var` has multiple lines for each subject*
2. dataset: `data = .` *we use the period to signify that the datset is being piped from above*
Here is an outline of what your syntax should look like when you **fit and save a RM ANOVA**. Of course you will replace the dataset name and the variable names, as well as the name you are saving it as.
```{block type='rmdlightbulb', echo=TRUE}
**NOTE:** The `aov_4()` function works on data in LONG format only. Each observation needs to be on its one line or row with seperate variables for the group membership (categorical factor or `fct`) and the continuous measurement (numberic or `dbl`).
```
```{r EX_RMaov_4, eval=FALSE}
# RM ANOVA: fit and save
aov_name <- data_name %>%
afex::aov_4(continuous_var ~ 1 + (RM_var|id_var),
data = .)
```
By running the name you saved you model under, you will get a brief set of output, including a measure of **Effect Size**.
```{block type='rmdlightbulb', echo=TRUE}
**NOTE:** The `ges` is the *generalized eta squared*. In a one-way ANOVA, the eta-squared effect size is the same value, ie. generalized $\eta_g$ and partial $\eta_p$ are the same.
```
```{r EX_RMaov_4_brief, eval=FALSE}
# Display basic ANOVA results (includes effect size)
aov_name
```
To fully fill out a standard ANOVA table and compute other effect sizes, you will need a more complete set of output, including the **Sum of Squares** components, you will need to add `summary()` piped at the end of the model name before running it or after the model with a pipe.
```{block type='rmdlightbulb', echo=TRUE}
**NOTE:** IGNORE the first line that starts with `(Intercept)`! Also, the 'mean sum of squares' are not included in this table, nor is the **Total** line at the bottom of the standard ANOVA table. You will need to manually compute these values and add them on the homework page. Remember that `Sum of Squares (SS)` and `degrees of freedom (df)` add up, but `Mean Sum of Squreas (MS)` do not add up. Also: `MS = SS/df` for each term.
```
This also runs and displays the results of Mauchly Tests for Sphericity, as well as the Greenhouse-Geisser (GG) and Huynh-Feldt (HF) Corrections to the p-value. If the Mauchly's p-value is bigger than .05, do not use the corrections. If Mauchly's p-value is less than .05, then apply the epsilon (`eps` or $\epsilon$) to multiply the degree's of freedom. Yes, the df will be decimal numbers.
```{r EX_RMaov_4_fuller, eval=FALSE}
# Display fuller ANOVA results (sphericity tests)
summary(aov_name)
```
To see all the Sumes-of-Squared residuals for ALL of the model comoponents, you add `$aov` at the end of the model name.
```{r EX_RMaov_4_ss, eval=FALSE}
# Display all the sum of squares
aov_name$aov
```
Repeated Measures MANOVA Tests (Pillai test statistic) is computed is you add `$Anova` at the end of the model name. This is a so called 'Multivariate Test'. **This is NOT what you want to do!**
```{r EX_RMaov_4_mult, eval=FALSE}
# Display fuller ANOVA results (includes sum of squares)
aov_name$Anova
```
If you only need to obtain the omnibus (overall) F-test without a correction for violation of sphericity, you can add an option for `correction = "none"`. You can also request both the generalized and partial $\eta^2$ effect sizes with `es = c("ges", "pes")`.
```{r EX_RMaov_5, eval=FALSE}
# RM ANOVA: no correction, both effect sizes
data_name %>%
afex::aov_4(continuous_var ~ 1 + (RM_var|id_var),
data = .,
anova_table = list(correction = "none",
es = c("ges", "pes")))
```
Post Hoc tests may be ran the same way as the 1 and 2-way ANOVAs from the last unit.
```{block type='rmdlightbulb', echo=TRUE}
**NOTE:** Use Fisher's LSD (`adjust = "none"`) if the omnibus F-test is significant AND there are THREE measurements per subject or block. Tukey's HSD (`adjust = "tukey"`) may be used even if the F-test is not significant or if there are four or more repeated measures.
```
```{r EX_RMaov_6lsd, eval=FALSE}
# RM ANOVA: post hoc all pairwise tests with Fisher's LSD correction
aov_name %>%
emmeans::emmeans(~ RM_var) %>%
pairs(adjust = "none")
```
```{r EX_RMaov_6hsd, eval=FALSE}
# RM ANOVA: post hoc all pairwise tests with Tukey's HSD correction
aov_name %>%
emmeans::emmeans(~ RM_var) %>%
pairs(adjust = "tukey")
```
A means plot (model based) can help you write up your results. This zooms in on just the means and will make all differences seem significant, so make sure to interpret it in conjunction with the ANOVA and post hoc tests.
```{r EX_RMaov_7, eval=FALSE}
# RM ANOVA: means plot
aov_name %>%
emmeans::emmip(~ RM_var)
```
## Words Recalled Data Example (Chapter 15, section A)
### Data Prep
I input the data as a `tribble` which saves it as a `data.frame` and then cleaned up a few of the important variables.
```{r, warning=FALSE, message=FALSE}
d <- tibble::tribble(
~ID, ~word_type, ~words_recalled,
1, 1, 20,
2, 1, 16,
3, 1, 8,
4, 1, 17,
5, 1, 15,
6, 1, 10,
1, 2, 21,
2, 2, 18,
3, 2, 7,
4, 2, 15,
5, 2, 10,
6, 2, 4,
1, 3, 17,
2, 3, 11,
3, 3, 4,
4, 3, 18,
5, 3, 13,
6, 3, 10) %>%
mutate(word_type = factor(word_type,
labels = c("Neutral", "Positive", "Negative"))) %>%
mutate(fake_id = row_number())
d
```
### One-Way Independent ANOVA
First, let's ignore the fact that we know this has repeated measures. As such, we will assume that each word type group is independent. Let's look at what happens:
```{r}
ind_anova <- d %>%
afex::aov_4(words_recalled ~ word_type + (1|fake_id),
data = .)
ind_anova$Anova
```
If we ignored that the `word_type` groups are not independent, we get an F-statistic = 0.272 and p = 0.765.
What do you think will happen if we account for the repeated measures? Will the F-statistic increase or decrease?
### One-Way RM ANOVA
Now, let's look at the repeated measures. We do this by using `afex::aov_4()` and then the `summary()` functions as shown below.
```{r}
oneway <- d %>%
afex::aov_4(words_recalled ~ 1 + (word_type|ID),
data = .)
summary(oneway)
```
Here, we see a number of pieces of information, including the sums of squares, F-statistic, and p-value. The F-statistic is now 1.195 and the p-value (although still not signfiicant) is .342. So what happened to the F-statistic? It decreased! So by using the information that these are repeated measures, we have more power. Why is that?
If we look at the output for the two ANOVAs above, both have the sums of squares for `word_type` at 16.33 so that didn't change at all. So what did change? Well, it comes down to understanding what is happening to the error term. Although not shown explicitly in the tables above, consider that:
$$
\text{Independent ANOVA: } SS_{total} = SS_{bet} + SS_w
$$
$$
\text{RM ANOVA: } SS_{total} = SS_{RM} + SS_{sub} + SS_{inter}
$$
$SS_{total}$ is the same in both and $SS_{RM} = SS_{RM}$ so what we are doing is splitting up the $SS_w$ into $SS_{sub} + SS_{inter}$ where only the $SS_{inter}$ is the error term now.
This means we have more power with the same amount of data.
Let's now plot this using a spaghetti plot.
```{r, warning=FALSE, message=FALSE}
d %>%
ggplot(aes(word_type, words_recalled, group = ID)) +
geom_line() +
geom_point()
```
The output provides us with a bit of an understanding of why there is not a significant effect of `word_type`. In addition to a spaghetti plot, it is often useful to show what the overall repeated measure factor is doing, not the individuals (especially if your sample size is larger than 20). To do that, we can use:
```{r}
oneway %>%
emmeans::emmip(~ word_type)
```
Although there is a pattern here, we need to consider the scale. Looking at the spaghetti plot, we have individuals that range from 5 to 20 so a difference of 2 or 3 is not large. However, it is clear that a pattern may exist and so we should probably investigate this further, possibly with a larger sample size.
## Another Example - Weight Loss
Contrived data on weight loss and self esteem over three months, for three groups of individuals: Control, Diet and Diet + Exercise. The data constitute a double-multivariate design.
### Restructure the data from wide to long format
Wide format
```{r}
head(carData::WeightLoss, n = 6)
```
Restructure
```{r}
WeightLoss_long <- carData::WeightLoss %>%
dplyr::mutate(id = row_number() %>% factor()) %>%
tidyr::gather(key = var,
value = value,
starts_with("wl"), starts_with("se")) %>%
tidyr::separate(var,
sep = 2,
into = c("measure", "month")) %>%
tidyr::spread(key = measure,
value = value) %>%
dplyr::select(id, group, month, wl, se) %>%
dplyr::mutate_at(vars(id, month), factor) %>%
dplyr::arrange(id, month)
```
Long format
```{r}
head(WeightLoss_long, n = 20)
```
Summary table
```{r}
WeightLoss_long %>%
dplyr::group_by(group, month) %>%
furniture::table1(wl, se)
```
```{r}
carData::WeightLoss %>%
dplyr::group_by(group) %>%
furniture::table1(wl1, wl2, wl3,
se1, se2, se3)
```
Raw data plot (exploratory)
```{r}
WeightLoss_long %>%
tidyr::gather(key = measure,
value = value,
wl, se) %>%
dplyr::mutate(measure = fct_recode(measure,
"Weight Loss, lb" = "wl",
"Self Esteem Rating" = "se")) %>%
ggplot(aes(x = month,
y = value %>% as.numeric %>% jitter,
group = id)) +
facet_grid(measure ~ group,
scale = "free_y",
switch = "y") +
geom_line() +
theme_bw() +
labs(x = "Time, months",
y = "Measurement Value",
title = "Person Profile Plot",
subtitle = "Raw Data")
```
### Does weight long change over time?
Fit the model
```{r}
fit_wl <- WeightLoss_long %>%
afex::aov_4(wl ~ 1 + (month|id),
data = .)
```
Brief output - uses the Greenhouse-Geisser correction for violations of sphericity (by default)
```{r}
fit_wl
```
Lots more output
```{r}
summary(fit_wl)
```
To force no GG correction:
```{r}
fit_wl_noGG <- WeightLoss_long %>%
afex::aov_4(wl ~ 1 + (month|id),
data = .,
anova_table = list(correction = "none"))
fit_wl_noGG
```
To request BOTH effect sizes: partial eta squared and generalized eta squared
```{r}
fit_wl_2es <- WeightLoss_long %>%
afex::aov_4(wl ~ 1 + (month|id),
data = .,
anova_table = list(es = c("ges", "pes")))
fit_wl_2es
```
To force no GG correction AND request BOTH effect sizes
```{r}
fit_wl_noGG <- WeightLoss_long %>%
afex::aov_4(wl ~ 1 + (month|id),
data = .,
anova_table = list(correction = "none",
es = c("ges", "pes")))
fit_wl_noGG
```
Estimated marginal means
```{r}
fit_wl %>%
emmeans::emmeans(~ month)
```
pairwise post hoc
```{r}
fit_wl %>%
emmeans::emmeans(~ month) %>%
pairs(adjust = "none")
```
means plot
```{r}
fit_wl %>%
emmeans::emmip( ~ month)
```