STA4173: Biostatistics
Spring 2025
Recall that ANOVA allows us to compare the means of three or more groups.
In one-way ANOVA, we are only considering one factor (grouping variable).
Now, we will discuss two-way ANOVA, which allows us to consider a second factor (grouping variable).
We now partition the SSTrt into the different factors under consideration.
Recall that SSE is the “catch all” for unexplained variance.
Let’s discuss some of the language used in two-way ANOVA.
Factor A has a levels.
Factor B has b levels.
There are ab treatment groups.
Now that we are including two factors, we must consider the interaction between the two.
In our example, suppose we are looking at weight as the outcome. If an interaction is detected,
Source | Sum of Squares | df | Mean Square | F |
---|---|---|---|---|
A | SSA | dfA | MSA | FA |
B | SSB | dfB | MSB | FB |
AB | SSAB | dfAB | MSAB | FAB |
Error | SSE | dfE | MSE | |
Total | SSTot | dfTot |
Let there be a levels of factor A and b levels of factor B.
\text{MS}_{\text{X}} = \frac{\text{SS}_{\text{X}}}{\text{df}_{\text{X}}}
\text{F}_{\text{X}} = \frac{\text{MS}_{\text{X}}}{\text{MS}_{\text{E}}}
We will again use lm()
and anova()
OR aov()
and summary()
to analyze the data.
aov()
.As we are specifying our model, we will:
library(tidyverse)
example <- tibble(birthweight = c(157.78, 136.79, 138.84, # age 20-29, diet 1
139.72, 125.47, 117.14, # age 20-29, diet 2
129.35, 110.73, 118.38, # age 20-29, diet 3
137.07, 146.28, 130.27, # age 30-39, diet 1
117.46, 128.54, 99.16, # age 30-39, diet 2
97.43, 125.26, 115.42), # age 30-39, diet 3
diet = as.factor(c(1, 1, 1,
2, 2, 2,
3, 3, 3,
1, 1, 1,
2, 2, 2,
3, 3, 3)),
age = as.factor(c(20, 20, 20,
20, 20, 20,
20, 20, 20,
30, 30, 30,
30, 30, 30,
30, 30, 30)))
Df Sum Sq Mean Sq F value Pr(>F)
diet 2 2104.7 1052.3 7.555 0.00752 **
age 1 332.0 332.0 2.384 0.14855
diet:age 2 32.5 16.3 0.117 0.89083
Residuals 12 1671.5 139.3
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Hypotheses
Test Statistic and p-Value
Rejection Region
Let’s now test the interaction between age and diet. Test at the \alpha=0.05 level.
Here’s the information we need:
Hypotheses
Test Statistic
Rejection Region
Conclusion/Interpretation
What happens after testing for an interaction?
If significant (reject H_0), we can:
If not significant (FTR H_0), we should remove the interaction term so that we can test and interpret the main effects.
Remember that we cannot look at main effects (A and B alone) when the interaction is included in the model!
If the interaction term is not significant, it should be removed from the ANOVA table so that we can test and interpret the main effects.
How to remove an interaction:
In R, we just literally remove the interaction term.
#m <- aov(birthweight ~ diet + age + diet:age, data = example)
m <- aov(birthweight ~ diet + age, data = example)
summary(m)
Df Sum Sq Mean Sq F value Pr(>F)
diet 2 2105 1052.3 8.646 0.00359 **
age 1 332 332.0 2.728 0.12085
Residuals 14 1704 121.7
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Now that we have removed the interaction term, we can test for main effects.
Hypotheses
Test Statistic and p-Value
Rejection Region
A note on hypotheses – we are writing them in sentence form here, however, we can write them mathematically.
For Factor A with a levels,
For Factor B with b levels,
We now want to determine if there are main effects of age and diet. Test at the \alpha=0.05 level.
The ANOVA table without the interaction term:
Hypotheses
Test Statistic and p-Value
Rejection Region
Conclusion/Interpretation
Hypotheses
Test Statistic and p-Value
Rejection Region
Conclusion/Interpretation
In this lecture, we are discussing two-way ANOVA.
So far, we have learned how to:
Now we will learn how to best communicate results, whether it’s the resulting interaction term or main effects.
If we detect an interaction term, we must give meaning to it.
An easy way to do this is to plot the treatment group means to visualize the interaction. This is called a profile plot.
y-axis: always the average outcome
x-axis: either factor A or B
if only one factor is ordinal, use it for the x-axis
if there are two ordinal or two nominal factors, select the factor with the largest number of levels for the x-axis
lines on the plot: the factor that was not selected for the x-axis
Note that this is just a graph of the means – it’s valid to construct a profile plot even if the interaction is not sigificant.
The average birth weight will go on our y-axis.
Because only maternal age is ordinal, it will go on our x-axis.
Thus, the lines will represent the diet
Building profile plots using ggplot()
is a process.
geom_line()
for each level of the factor creating the lines.Building profile plots using ggplot()
is a process.
geom_line()
for each level of the factor creating the lines.Building profile plots using ggplot()
is a process.
geom_text()
to label each line (use the line colors to make sure everything is labeled properly).graph %>%
ggplot(aes(x = age, group = 1)) +
geom_line(aes(y = d1), color = "pink") + # diet 1
geom_line(aes(y = d2), color = "purple") + # diet 2
geom_line(aes(y = d3), color = "blue") + # diet 3
geom_text(aes(x = "30" , y = 137, label = "Diet 1"), color = "pink") + # diet 1
geom_text(aes(x = "30" , y = 115, label = "Diet 2"), color = "purple") + # diet 2
geom_text(aes(x = "30" , y = 110, label = "Diet 3"), color = "blue") + # diet 3
theme_bw()
Building profile plots using ggplot()
is a process.
geom_text()
to label each line (use the line colors to make sure everything is labeled properly).Building profile plots using ggplot()
is a process.
graph %>%
ggplot(aes(x = age, group = 1)) +
geom_line(aes(y = d1), color = "black") + # diet 1
geom_line(aes(y = d2), color = "black") + # diet 2
geom_line(aes(y = d3), color = "black") + # diet 3+
geom_text(aes(x = "30" , y = 137, label = "Diet 1"), color = "black") + # diet 1
geom_text(aes(x = "30" , y = 115, label = "Diet 2"), color = "black") + # diet 2
geom_text(aes(x = "30" , y = 110, label = "Diet 3"), color = "black") + # diet 3
labs(x = "Maternal Age",
y = "Average Birth Weight") +
theme_bw()
We can also apply posthoc testing to two-way ANOVA.
If the interaction is significant, we can compare all treatment groups.
If the interaction is not significant, we can look at the main effects.
For simplicity, we will only look at Tukey’s and Fisher’s.
What is the difference between Tukey’s and Fisher’s?
Let’s apply Tukey’s to the model with the interaction term.
diff lwr upr p adj
2:20-1:20 -17.026667 -49.39499 15.341659 0.51846118
3:20-1:20 -24.983333 -57.35166 7.384992 0.17257865
1:30-1:20 -6.596667 -38.96499 25.771659 0.98037295
2:30-1:20 -29.416667 -61.78499 2.951659 0.08309462
3:30-1:20 -31.766667 -64.13499 0.601659 0.05550164
3:20-2:20 -7.956667 -40.32499 24.411659 0.95688090
1:30-2:20 10.430000 -21.93833 42.798326 0.87931551
2:30-2:20 -12.390000 -44.75833 19.978326 0.78717713
3:30-2:20 -14.740000 -47.10833 17.628326 0.65386678
1:30-3:20 18.386667 -13.98166 50.754992 0.44210449
2:30-3:20 -4.433333 -36.80166 27.934992 0.99674874
3:30-3:20 -6.783333 -39.15166 25.584992 0.97786517
2:30-1:30 -22.820000 -55.18833 9.548326 0.24087234
3:30-1:30 -25.170000 -57.53833 7.198326 0.16754036
3:30-2:30 -2.350000 -34.71833 30.018326 0.99984711
diff lwr upr p adj
2-1 -19.923333 -36.59450 -3.252169 0.01902981
3-1 -25.076667 -41.74783 -8.405502 0.00397944
3-2 -5.153333 -21.82450 11.517831 0.70372399
We did not explicitly discuss assumptions, but they are the same as in one-way ANOVA.
From here, ANOVA can be used to specify any model.
We stopped with two-way interactions, but you could have three-way interactions, four-way interactions, etc.
As you can see, interactions quickly complicate the interpretation, so I try to stay away from anything higher than a two-way interaction.
Our next module is regression analysis, which is the same as ANOVA, but represents the results differently.