This example is taken from https://crumplab.github.io/statisticsLab/lab-10-factorial-anova.html#r-10. The data file is stroop_stand.csv, and can be found here https://github.com/CrumpLab/statisticsLab/tree/master/data

Load the data and libraries you will use

library(data.table)
library(dplyr)
library(ggplot2)
all_data <- fread("data/stroop_stand.csv")

Pre-processing

Pre-processing can include many steps. Here, we convert the data from wide to long format, and make a new data frame.

RTs <- c(as.numeric(unlist(all_data[,1])),
         as.numeric(unlist(all_data[,2])),
         as.numeric(unlist(all_data[,3])),
         as.numeric(unlist(all_data[,4]))
         )

Congruency <- rep(rep(c("Congruent","Incongruent"),each=50),2)
Posture <- rep(c("Stand","Sit"),each=100)
Subject <- rep(1:50,4)

stroop_df <- data.frame(Subject,Congruency,Posture,RTs)

Checks

It is important to check the data you are analyzing before you analyze. The checks you make depend on the questions you are trying to answer. Here we check the number of subjects in each condition.

This is a 2x2 repeated measures design, each subject should have 4 means

num_subjects <- stroop_df %>%
                  group_by(Subject) %>%
                  summarise(counts = length(Subject))
total_subjects <- length(num_subjects$Subject)
total_has_four <- num_subjects$counts == 4
sum(total_has_four) == total_subjects
## [1] TRUE

Are there any subjects with huge mean RTs? Could indicate something weird happened.

hist(stroop_df$RTs)

Exclusion

If you are going to exlcude subjects from analysis, then justify your exclusion criterion, and then eliminate subject data from the data frame. We will not exlcude any subjects here.

# no exclusions

Analysis

Now you can begin analysis. This experiment asked whether the Stroop effect (difference between mean incongruent and mean congruent), depends on whether people are sitting or standing. Let’s get the means, make a tbale, then plot the data.

table of 2x2 means

overall_means <- stroop_df %>%
                  group_by(Posture,Congruency) %>%
                  summarise(meanRT = mean(RTs),
                            SEMRT = (sd(RTs)/sqrt(length(RTs))))

# make a table of overall means
knitr::kable(overall_means)
Posture Congruency meanRT SEMRT
Sit Congruent 821.9232 16.60384
Sit Incongruent 940.7855 17.91041
Stand Congruent 807.9599 14.93521
Stand Incongruent 903.9131 15.34939

plot of 2x2 means

ggplot(overall_means, aes(x=Posture,
                          y=meanRT, 
                          group=Congruency,
                          fill=Congruency))+
  geom_bar(stat="identity",position="dodge")+
  theme_classic(base_size=12)+
  ylab("Mean Reaction Time (ms)")+
  geom_errorbar(aes(ymin=meanRT-SEMRT,
                    ymax=meanRT+SEMRT),
                position=position_dodge(width=0.9),
                width=.2,
                color="black")+
  coord_cartesian(ylim=c(750,1000))

Repeated measure ANOVA

# Make sure Subjecdt is  a factor
stroop_df$Subject <-  as.factor(stroop_df$Subject)

aov_out <- aov(RTs~Posture*Congruency + Error(Subject/(Posture*Congruency)), stroop_df)
#print summary of ANOVA table
summary(aov_out)
## 
## Error: Subject
##           Df  Sum Sq Mean Sq F value Pr(>F)
## Residuals 49 2250739   45933               
## 
## Error: Subject:Posture
##           Df Sum Sq Mean Sq F value  Pr(>F)   
## Posture    1  32303   32303    7.33 0.00931 **
## Residuals 49 215948    4407                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Error: Subject:Congruency
##            Df Sum Sq Mean Sq F value Pr(>F)    
## Congruency  1 576822  576822   342.5 <2e-16 ***
## Residuals  49  82535    1684                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Error: Subject:Posture:Congruency
##                    Df Sum Sq Mean Sq F value  Pr(>F)   
## Posture:Congruency  1   6560    6560   8.964 0.00431 **
## Residuals          49  35859     732                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# prints a nicer ANOVA table
summary_out <- summary(aov_out)
library(xtable)
knitr::kable(xtable(summary_out))
Df Sum Sq Mean Sq F value Pr(>F)
Residuals 49 2250738.636 45933.4416 NA NA
Posture 1 32303.453 32303.4534 7.329876 0.0093104
Residuals1 49 215947.614 4407.0942 NA NA
Congruency 1 576821.635 576821.6349 342.452244 0.0000000
Residuals 49 82534.895 1684.3856 NA NA
Posture:Congruency 1 6560.339 6560.3389 8.964444 0.0043060
Residuals 49 35859.069 731.8177 NA NA
# prints means for each effect
print(model.tables(aov_out,"means"), format="markdown")
## Tables of means
## Grand mean
##          
## 868.6454 
## 
##  Posture 
## Posture
##   Sit Stand 
## 881.4 855.9 
## 
##  Congruency 
## Congruency
##   Congruent Incongruent 
##       814.9       922.3 
## 
##  Posture:Congruency 
##        Congruency
## Posture Congruent Incongruent
##   Sit   821.9     940.8      
##   Stand 808.0     903.9

Example Write-Up

We submitted the mean reaction times for each subject in each condition to a 2 (Congruency: congruecnt vs. incongruent) x 2 (Posture: Standing vs. Sitting) repeated measures ANOVA.

There was a main effect of Congruency, F (1, 49) = 342.45, MSE = 1684.39, p < 0.001. Mean reaction times were slower for incongruent (922 ms) than congruent groups (815 ms).

There main effect of Posture was significant, F (1, 49) = 7.33, MSE = 4407.09, p =.009. Mean reaction times were slower for sitting (881 ms) than standing groups (855 ms).

The two-way interaction between Congruency and Posture was significant, F (1, 49) = 8.96, MSE = 731.82, p < 0.004. The Stroop effect was 23 ms smaller in the standing than sitting conditions.