In this handout I will show how the results from a factorial ANOVA can be reproduced in a one-way ANOVA that decomposes the variation associated with groups into components corresponding to main effects and interactions.

load(file=url("http://pnb.mcmaster.ca/bennett/psy710/datasets/2x2-aov.rda"))
summary(df0)
##   A       B            Y         
##  a1:20   b1:20   Min.   : 58.92  
##  a2:20   b2:20   1st Qu.: 78.13  
##                  Median :100.50  
##                  Mean   :101.07  
##                  3rd Qu.:121.45  
##                  Max.   :177.35

First I will use a factorial ANOVA to analyze data that were collected using a 2(A) x 2(B) between-subjects design. The data are stored in the data frame df0. Figure 1 shows the distributions of data in each cell, or condition. The marginal distributions of A and B are shown in Figure 2. It looks like the marginal means of A, but not B differ. Figure 1 suggests that the difference between a1 and a2 depends on the level of B. This last effect is more noticeable in Figure 3.

# create figures 1 & 2:
boxplot(Y~A*B,df0,main="A x B Cell Means")
par(mfrow=c(1,2))
boxplot(Y~A,df0,main="Main Effect of A")
boxplot(Y~B,df0,main="Main Effect of B")
Figure 1. Data from the 2 x 2 design.

Figure 1. Data from the 2 x 2 design.

Figure 2. Marginal distributions of A and B.

Figure 2. Marginal distributions of A and B.

# CREATE FIGURE 3:
# see: https://rpubs.com/tf_peterson/interactionplotDemo
with(df0,interaction.plot(x.factor=A,
                          trace.factor=B,
                          response=Y,
                          ylab=expression(bar(Y)),
                          cex.lab=1.5,
                          cex.axis=1.5))
axis(side=1,at=c(1,2),labels=F)
Figure 3. Interaction plot.

Figure 3. Interaction plot.

The ANOVA table is shown below. The main effect of A is significant, but the main effect of B is not. The AxB interaction is significant, indicating that the effect of A depends on the level of B and, equivalently, that the effect of B depends on the level of A.

options(contrasts=c("contr.sum","contr.poly"))
aov.01 <- aov(Y~A*B,data=df0)
# summary(aov.01)
kable(anova(lm(Y~A*B,data=df0)),
    format="pipe",
    padding=2,
    caption="2 x 2 Factorial ANOVA table.",
    format.args=list(digits=2,justify="left"),
    align='l',
    col.names=c("df","SS","MS","F","p"))
2 x 2 Factorial ANOVA table.
df SS MS F p
A 1 5096 5096 10.36 0.0027
B 1 284 284 0.58 0.4521
A:B 1 4302 4302 8.75 0.0054
Residuals 36 17705 492 - -

Compare factorial & one-way ANOVAs

In this section we will show that the results of the between-groups, 2x2 ANOVA are equivalent to performing 3 orthogonal contrasts on a one-way, four-group design. In everything that follows, we assume that the design is balanced.

First we need to transform our 2 x 2 into a one-way design with four conditions/groups. We will use this new grouping variable, condition, to illustrate how our 2x2 ANOVA can be re-created using linear contrasts.

df0$condition <- interaction(df0$A,df0$B) # new factor
summary(df0)
##   A       B            Y          condition 
##  a1:20   b1:20   Min.   : 58.92   a1.b1:10  
##  a2:20   b2:20   1st Qu.: 78.13   a2.b1:10  
##                  Median :100.50   a1.b2:10  
##                  Mean   :101.07   a2.b2:10  
##                  3rd Qu.:121.45             
##                  Max.   :177.35
sapply(df0,class)
##         A         B         Y condition 
##  "factor"  "factor" "numeric"  "factor"

Next, we compare results from 2x2 and one-way ANOVAs.

aov.2x2 <- aov(Y~A + B + A:B,data=df0)
aov.oneway <- aov(Y~condition,data=df0)
summary(aov.2x2)
##             Df Sum Sq Mean Sq F value  Pr(>F)   
## A            1   5096    5096  10.361 0.00273 **
## B            1    284     284   0.578 0.45207   
## A:B          1   4302    4302   8.747 0.00545 **
## Residuals   36  17705     492                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
summary(aov.oneway)
##             Df Sum Sq Mean Sq F value  Pr(>F)   
## condition    3   9682    3227   6.562 0.00118 **
## Residuals   36  17705     492                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Notice that SS-residuals are the same in the two analyses. Also SS-condition equals \(\mathit{SS}_A\) + \(\mathit{SS}_B\) + \(\mathit{SS}_\mathit{AxB}\). This is our first clue that the 2x2 ANOVA decomposes the variation association with group/condition into 3 separate parts.

To illustrate this idea we will decompose the one-way effect of condition with the orthogonal contrasts shown in Table 1.

Table 1. Complete set of orthogonal contrasts.

Table 1. Complete set of orthogonal contrasts.

Main effects of A & B

Contrast cA compares the mean of a1 to the mean in a2. This contrast is equivalent to the main effect of A.

levels(df0$condition)
## [1] "a1.b1" "a2.b1" "a1.b2" "a2.b2"
cA <- c(-1,1,-1,1)
contrasts(df0$condition) <- cA
aov.oneway.cA <- aov(Y~condition,data=df0)
summary(aov.oneway.cA,split=list(condition=list(cA=1)))
##                 Df Sum Sq Mean Sq F value  Pr(>F)   
## condition        3   9682    3227   6.562 0.00118 **
##   condition: cA  1   5096    5096  10.361 0.00273 **
## Residuals       36  17705     492                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Notice that results of our contrast (SS, F, and p) are the same as the main effect of A in the 2x2 ANOVA. Contrast cB compares the mean of b1 to the mean in b2. This is equivalent to the main effect of B. The results of that contrast (SS, F, and p) are the same as the main effect of B in the 2x2 ANOVA.

levels(df0$condition)
## [1] "a1.b1" "a2.b1" "a1.b2" "a2.b2"
cB <- c(-1,-1,1,1)
contrasts(df0$condition) <- cB
aov.oneway.cB <- aov(Y~condition,data=df0)
summary(aov.oneway.cB,split=list(condition=list(cB=1)))
##                 Df Sum Sq Mean Sq F value  Pr(>F)   
## condition        3   9682    3227   6.562 0.00118 **
##   condition: cB  1    284     284   0.578 0.45207   
## Residuals       36  17705     492                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

A x B Interaction

Finally, we must construct a contrast that is equivalent to the AxB interaction effect in the factorial ANOVA. To do this, we need to remember that the AxB interaction tests the hypothesis that the effect of A differs across the levels of B/ The effect of A in conditions a1b1 and a2b1 can be examined with contrast weights [-1,1,0,0], and the effect of A in conditions a1b2 and a2b2 can be evaluated with weights [0,0,-1,1]. Finally, the difference between those two effects is [-1,1,0,0] - [0,0,-1,1], or [-1,1,1,-1], which corresponds to contrast cAxB in Table 1. Notice that this set of weights can also be used to test the hypothesis that the effect of B differs between a1 and a2. In this 2x2 design, where each main effect corresponds to a single contrast, the interaction contrast also can be obtained simply by multiplying the contrasts for the two main effects: cAxB = cA x cB.

Now we perform the contrast:

levels(df0$condition)
## [1] "a1.b1" "a2.b1" "a1.b2" "a2.b2"
( cAxB <- cA*cB )
## [1]  1 -1 -1  1
contrasts(df0$condition) <- cAxB
aov.oneway.cAxB <- aov(Y~condition,data=df0)
summary(aov.oneway.cAxB,split=list(condition=list(cAxB=1)))
##                   Df Sum Sq Mean Sq F value  Pr(>F)   
## condition          3   9682    3227   6.562 0.00118 **
##   condition: cAxB  1   4302    4302   8.747 0.00545 **
## Residuals         36  17705     492                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Notice that results of our contrast (SS, F, and p) are the same as the AxB interaction in the 2x2 ANOVA.

Finally, we can reproduce the factorial ANOVA table by conducting the 3 contrasts simultaneously.

contrasts(df0$condition) <- cbind(cA,cB,cAxB)
aov.oneway.all <- aov(Y~condition,data=df0)
summary(aov.oneway.all,split=list(condition=list(cA=1,cB=2,cAxB=3)))
##                   Df Sum Sq Mean Sq F value  Pr(>F)   
## condition          3   9682    3227   6.562 0.00118 **
##   condition: cA    1   5096    5096  10.361 0.00273 **
##   condition: cB    1    284     284   0.578 0.45207   
##   condition: cAxB  1   4302    4302   8.747 0.00545 **
## Residuals         36  17705     492                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

2 x 3 factorial ANOVA

We now consider how the previous analysis can be applied to data collected using a 2 x 3 factorial design to measure recall memory in young and old adults with three study-test retention intervals (0, 10, and 20 minutes). The data are loaded with the following commands:

load(file=url("http://pnb.mcmaster.ca/bennett/psy710/datasets/memory-2x3.rda"))
summary(mem.df)
##       age     retention     memory     
##  older  :60   r0 :40    Min.   :17.58  
##  younger:60   r10:40    1st Qu.:26.31  
##               r20:40    Median :29.86  
##                         Mean   :30.00  
##                         3rd Qu.:33.64  
##                         Max.   :41.67
xtabs(~age+retention,data=mem.df)
##          retention
## age       r0 r10 r20
##   older   20  20  20
##   younger 20  20  20

There are 20 subjects per group. The 2 x 3 ANOVA (below) indicates that the main effect of age and the age x retention interaction are significant.

options(contrasts=c("contr.sum","contr.poly"))
mem.aov <- aov(memory~age*retention,data=mem.df)
summary(mem.aov)
##                Df Sum Sq Mean Sq F value Pr(>F)  
## age             1  142.7  142.74   6.515 0.0120 *
## retention       2  136.9   68.45   3.124 0.0478 *
## age:retention   2  197.7   98.84   4.511 0.0130 *
## Residuals     114 2497.7   21.91                 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

The interaction is illustrated in Figure 4. It appears that the effect of recall was larger in older adults compared to younger adults.

# create figure 4:
with(mem.df,interaction.plot(x.factor=retention,
                          trace.factor=age,
                          response=memory,
                          ylab="Memory",
                          cex.lab=1.5,
                          cex.axis=1.5))
Figure 4. 2 (age) x 3 (retention) memory study.

Figure 4. 2 (age) x 3 (retention) memory study.

Now we analyze the data as though it was collected using a one-way design. First we create a new factor, condition, that has one level for each combination of age and retention.

mem.df$condition <- interaction(mem.df$age,mem.df$retention)
summary(mem.df$condition)
##    older.r0  younger.r0   older.r10 younger.r10   older.r20 younger.r20 
##          20          20          20          20          20          20

Next we construct a set of contrasts to decompose the variation associated with condition into three, separate pieces corresponding to the main effects of age and retention, and the age x retention interaction. The contrasts are listed in Table 2. Contrast cAge compares older (a1) and younger (a2)adults, and therefore corresponds to the main effect of age. The factor retention has 3 levels and two degrees of freedom, and so we need two contrasts to capture the variation associated with the main effect of retention. Contrast cRet1 compares the r20 condition to the mean of the r0 and r10 conditions, and contrast cRet2 compares the r0 and r10 conditions.

The last two contrasts constitute the age \(\times\) retention interaction. Contrast cAxR1 asks if the difference between r20 and the mean of the r0 and r10 conditions differs between older (a1) and younger (a2) subjects. Contrast cAxR1 can be written as [1 0 1 0 -2 0] - [0 1 0 1 0 -2] = [1 -1 1 -1 -2 2]. Also, the weights of cAxR1 are equal to the product of cAge and cRet1. Finally, contrast cAxR2, which equals the product of cAge times cRet2, asks if the difference between ages in the r0 condition differs from the age difference in the r10 condition.

Table 2. Complete set of orthogonal contrasts for 2 x 3 design.

Table 2. Complete set of orthogonal contrasts for 2 x 3 design.

Here I create the contrasts and assign them to the condition factor.

levels(mem.df$age)
## [1] "older"   "younger"
# main effect of age:
cAge <- c(-1,1,-1,1,-1,1) # old vs young
levels(mem.df$retention)
## [1] "r0"  "r10" "r20"
# main effect of retention (df=2)
cRet1 <- c(-1,-1,-1,-1,2,2) # r20 vs (r0 + r10)/2
cRet2 <- c(-1,-1,1,1,0,0) # r0 vs r10
# age x retention interaction (df=2)
cAxR1 <- c(1,-1,1,-1,-2,2) # cAge x cRet1
cAxR2 <- c(1,-1,-1,1,0,0) # cAge x cRet2
cMat <- cbind(cAge,cRet1,cRet2,cAxR1,cAxR2)
( contrasts(mem.df$condition) <- cMat )
##      cAge cRet1 cRet2 cAxR1 cAxR2
## [1,]   -1    -1    -1     1     1
## [2,]    1    -1    -1    -1    -1
## [3,]   -1    -1     1     1    -1
## [4,]    1    -1     1    -1     1
## [5,]   -1     2     0    -2     0
## [6,]    1     2     0     2     0

Finally, I conduct the ANOVA and list the results. Note that the sums-of-squares for the constrasts are the same as the ones obtained in the 2 x 3 factorial ANOVA.

mem.1way.aov <- aov(memory~condition,data=mem.df)
summary(mem.1way.aov,split=list(condition=list(Age=1,Ret=2:3,Age_x_Ret=4:5)))
##                         Df Sum Sq Mean Sq F value  Pr(>F)   
## condition                5  477.3   95.46   4.357 0.00115 **
##   condition: Age         1  142.7  142.74   6.515 0.01202 * 
##   condition: Ret         2  136.9   68.45   3.124 0.04776 * 
##   condition: Age_x_Ret   2  197.7   98.84   4.511 0.01301 * 
## Residuals              114 2497.7   21.91                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Conclusion

In summary, factorial ANOVA is equivalent to analyzing a one-way design with contrasts that break the variation associated with conditions into separate pieces. Each line in the factorial ANOVA table can be thought of as representing the Sum-of-Squares associated with a family of 1 or more contrasts, and the families of contrasts representing the main effects and interaction are orthogonal to each other (and therefore account for independent sources of variation among the group means).