One option may be to use the afex package to fit the ANOVA and the marginaleffects package to compute contrasts. (Disclaimer: I am the author of marginaleffects.)
First, fit the model:
library(marginaleffects)
library(afex)
df = matrix(nrow = 120, ncol = 0)
df = as.data.frame(df)
df$ppt_id = c(rep(c(1:30),4))
df$color = c(rep("red", 60), rep("blue", 60))
df$side = c(rep(c(rep("left", 30), rep("right", 30)),2))
df$RT = c(rnorm(30, 600, 50),
rnorm(30, 650, 50),
rnorm(30, 700, 50),
rnorm(30, 600, 50))
mod <- aov_ez(
id = "ppt_id",
dv = "RT",
within = c("side", "color"),
data = df)
Then, do post hoc processing to get the contrasts. This will tell you how the values of the outcome predicted by the model change when we manipulate the explanators (and their pairwise combinations):
cmp <- comparisons(
# the model
mod,
# hold other regressors at their means if there are any
newdata = "mean",
# vector of variables we want to "manipulate" in the contrast
variables = c("side", "color"),
# we care about interactions
interactions = TRUE)
summary(cmp)
#> Average contrasts
#> side color Effect Std. Error z value Pr(>|z|)
#> 1 left - left blue - red 110.489 10.71 10.3148 < 2.22e-16
#> 2 right - left red - red 51.055 13.84 3.6896 0.00022457
#> 3 right - left blue - red 6.274 12.19 0.5147 0.60674367
#> 2.5 % 97.5 %
#> 1 89.49 131.48
#> 2 23.93 78.18
#> 3 -17.62 30.16
#>
#> Model type: afex_aov
#> Prediction type: response
There is a detailed vignette on contrasts here: https://vincentarelbundock.github.io/marginaleffects/articles/contrasts.html
Edit: A few notes on interpretation
Start by using the afex package’s default predict() method to compute predicted outcome for an individual with color blue and either values of the side variable:
nd <- data.frame(side = c("left", "right"), color = "blue", ppt_id = 1)
nd
#> side color ppt_id
#> 1 left blue 1
#> 2 right blue 1
predict(mod, newdata = nd)
#> 1 2
#> 678.7812 609.2096
The difference between left+blue and right+blue is:
diff(predict(mod, newdata = nd))
#> 2
#> -69.57154
We can obtain the same result with standard errors using the predictions() and comparisons() functions from marginaleffects():
predictions(mod, newdata = nd)
#> rowid type predicted std.error statistic p.value conf.low conf.high side color ppt_id
#> 1 1 response 678.7812 11.02046 61.59284 0 657.1815 700.3808 left blue 1
#> 2 2 response 609.2096 9.27690 65.66953 0 591.0272 627.3920 right blue 1
comparisons(
mod,
variables = "side",
newdata = datagrid(color = "blue", ppt_id = 1))
#> rowid type term contrast_side comparison std.error statistic p.value conf.low conf.high side color ppt_id
#> 1 1 response interaction right - left -69.57154 14.12988 -4.923717 8.491553e-07 -97.26559 -41.87748 left blue 1
This is a simple “contrast”: What happens to the predicted outcome when side changes from left to right, and color is equal to blue for individual 1?
Now we can compute the same contrast between “left” and “right”, but for individuals in either color conditions:
comparisons(
mod,
variables = "side",
newdata = datagrid(color = c("red", "blue"), ppt_id = 1))
#> rowid type term contrast_side comparison std.error statistic p.value conf.low conf.high side color ppt_id
#> 1 1 response interaction right - left 52.41222 13.74332 3.813650 1.369293e-04 25.47581 79.34864 left red 1
#> 2 2 response interaction right - left -69.57154 14.12988 -4.923717 8.491553e-07 -97.26559 -41.87748 left blue 1
In the first example I initially gave you, you see “interacted” contrasts: What happens when both the side and color variables change simultaneously?
I’m not an expert in the afex package, so you will have to refer to their documentation to answer the “within” question.