Two-Way ANOVA Example in R-Quick Guide

[This article was first published on, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

The post Two-Way ANOVA Example in R-Quick Guide appeared first on

Two-Way ANOVA Example in R, the two-way ANOVA test is used to compare the effects of two grouping variables (A and B) on a response variable at the same time.

Factors are another name for grouping variables. Levels are the several categories (groups) of a component. The number of levels varies depending on the element.

The balanced design occurs when the sample sizes within cells are equal. The conventional two-way ANOVA test can be used in this circumstance.

The ANOVA test should be handled differently when the sample sizes within each level of the independent variables are not the same (instance of unbalanced designs).

This article shows how to use R software to compute two-way ANOVA tests for balanced and unbalanced designs.

Hypotheses two-way ANOVA.

There is no difference in factor A’s means.

There is no difference in factor B’s means.

Factors A and B do not interact in any way.

For examples 1 and 2, the alternative hypothesis is that the means are not equal.

The alternate hypothesis for scenario 3 is that A and B have an interaction.

Two-way ANOVA test assumptions

Like all ANOVA tests, two-way ANOVA assumes that the observations within each cell are normally distributed with equal variances.

After fitting ANOVA, we’ll teach you how to double-check these assumptions.

In R, create a two-way ANOVA test using balanced designs

When we have equal sample sizes within levels of our independent grouping levels, we call it a balanced design.

We’ll use the built-in R data set ToothGrowth for this. It includes information from a study on the effects of vitamin C on tooth growth in Guinea pigs.

The trial used 60 pigs who were given one of three vitamin C dose levels (0.5, 1, or 2 mg/day) via one of two administration routes (orange juice or ascorbic acid) (a form of vitamin C and coded as VC).

A sample of the data is presented below, which includes tooth length measurements.

data <- ToothGrowth

We use the function sample n() [in the dplyr package] to display a random sample of the data to get an idea of how the data looks. Install dplyr first if you don’t already have it.


Show a random sample

dplyr::sample_n(data, 5)
    len supp dose
1 15.2   OJ  0.5
2 22.5   VC  1.0
3 25.5   OJ  2.0
4 17.3   VC  1.0
5  7.3   VC  0.5

Check the structure

'data.frame':      60 obs. of  3 variables:
 $ len : num  4.2 11.5 7.3 5.8 6.4 10 11.2 11.2 5.2 7 ...
 $ supp: Factor w/ 2 levels "OJ","VC": 2 2 2 2 2 2 2 2 2 2 ...
 $ dose: num  0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 ...

R treats “dose” as a numeric variable based on the output. As follows, we’ll transform it to a factor variable (i.e., grouping variable).

Recode the levels after converting the dose to a factor.

data$dose <- factor(data$dose,
                  levels = c(0.5, 1, 2),
                  labels = c("D0.5", "D1", "D2"))
data.frame':       60 obs. of  3 variables:
 $ len : num  4.2 11.5 7.3 5.8 6.4 10 11.2 11.2 5.2 7 ...
 $ supp: Factor w/ 2 levels "OJ","VC": 2 2 2 2 2 2 2 2 2 2 ...
 $ dose: Factor w/ 3 levels "D0.5","D1","D2": 1 1 1 1 1 1 1 1 1 1 ..

Question: Does tooth length vary according to on supp and dose?

Make frequency tables by:

     D0.5 D1 D2
  OJ   10 10 10
  VC   10 10 10

We have 2 X 3 design cells, each with 10 individuals and the factors supp and dose. We have a well-balanced design here.

Because this is the most basic situation, we’ll show how to analyze data from balanced designs in the next sections.

Visualize your information

To depict group differences, box plots and line plots can be used:

To visualize the data grouped by the levels of the two factors, use a box plot.

The mean (or another summary) of the answer for two-way combinations of factors is plotted in a two-way interaction plot, indicating probable interactions.

Read R base graphs to learn how to utilise them. For an easy ggplot2-based data visualisation, we’ll use the ggpubr R tool.


Visualize your data with ggpubr:

Plot tooth length (“len”) by groups (“dose”)

Color box plot by a second group: “supp”

ggboxplot(data, x = "dose", y = "len", color = "supp",
          palette = c("#00AFBB", "#E7B800"))

Add error bars: mean_se

ggline(data, x = "dose", y = "len", color = "supp",
       add = c("mean_se", "dotplot"),
       palette = c("#00AFBB", "#E7B800"))

Type the following scripts if you still want to utilize R basic graphs:

Box plot with two variable factors

boxplot(len ~ supp * dose, data=data, frame = FALSE,
        col = c("#00AFBB", "#E7B800"), ylab="Tooth Length")

Two-way interaction plot

interaction.plot(x.factor = data$dose, trace.factor = data$supp,
                 response = data$len, fun = mean,
                 type = "b", legend = TRUE,
                 xlab = "Dose", ylab="Tooth Length",
                 pch=c(1,19), col = c("#00AFBB", "#E7B800"))

Compute a two-way ANOVA test

We’re curious if tooth length is affected by supp and dose.

This question can be answered using the R function aov(). Summary of the function The analysis of the variance model is summarised using aov().

res.aov2 <- aov(len ~ supp + dose, data = data)
Df Sum Sq Mean Sq F value   Pr(>F)
supp         1  205.4   205.4   11.45   0.0013 **
dose         1 2224.3  2224.3  123.99 6.31e-16 ***
Residuals   57 1022.6    17.9                    
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

The columns F value and Pr(>F) in the output corresponding to the p-value of the test.

We may deduce from the ANOVA table that both supp and dose are statistically significant. The most important factor variable is dosage.

These findings led us to anticipate that modifying the delivery technique (supp) or the vitamin C dose will have a major impact on the mean tooth length.

The above-fitted model is not referred to as an additive model. It is presumptively assumed that the two-factor variables are unrelated.

Replace the plus symbol (+) with an asterisk (*) if you think these two variables will interact to create a synergistic effect.

res.aov3 <- aov(len ~ supp * dose, data = data)
            Df Sum Sq Mean Sq F value   Pr(>F) 
supp         1  205.4   205.4  12.317 0.000894 ***
dose         1 2224.3  2224.3 133.415  < 2e-16 ***
supp:dose    1   88.9    88.9   5.333 0.024631 * 
Residuals   56  933.6    16.7                    
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

The two primary effects (supplement and dose), as well as their interaction, are statistically significant.

In situations where the interaction is not significant, the additive model should be used.

Interpret the outcomes

Based on the p-values and a significance level of 0.05, you may deduce the following from the ANOVA results:

Supp has a p-value <0.05 (significant), indicating that varied levels of supp are associated with varying tooth lengths.

The dose p-value <0.05 (significant), indicating that differing treatment levels are linked with substantial differences in tooth length.

The interaction between supp*dose has a p-value of 0.02 (significant), indicating that the connection between dose and tooth length is influenced by the supp technique.

Make a few summary statistics.

Using the dplyr R package, compute mean and SD by groups:

group_by(data, supp, dose) %>%
    count = n(),
    mean = mean(len, na.rm = TRUE),
    sd = sd(len, na.rm = TRUE)
supp   dose count  mean    sd
  <fct> <dbl> <int> <dbl> <dbl>
1 OJ      0.5    10 13.2   4.46
2 OJ      1      10 22.7   3.91
3 OJ      2      10 26.1   2.66
4 VC      0.5    10  7.98  2.75
5 VC      1      10 16.8   2.52
6 VC      2      10 26.1   4.80

Multiple pairwise comparisons between group means

A significant p-value in an ANOVA test shows that some of the group means differ, but we don’t know which pairings of groups differ.

Multiple pairwise comparisons can be performed to see if the mean differences between certain pairs of groups are statistically significant.

Multiple Tukey pairwise comparisons

We can compute Tukey HSD (Tukey Honest Significant Differences, R function: TukeyHSD()) for doing numerous pairwise comparisons between the means of groups because the ANOVA test is significant.

The fitted ANOVA is passed to TukeyHD() as an input.

We don’t need to run the test on the “supp” variable because it only has two levels, both of which have been shown to be substantially different using the ANOVA test.

As a result, the Tukey HSD test will only be performed on the factor variable “dosage.”

Best Data Science YouTube Tutorials Free to Learn

TukeyHSD(res.aov3, which = "dose")
  Tukey multiple comparisons of means
    95% family-wise confidence level
Fit: aov(formula = len ~ supp + dose + supp:dose, data = data)
          diff       lwr       upr   p adj
D1-D0.5  9.130  6.362488 11.897512 0.0e+00
D2-D0.5 15.495 12.727488 18.262512 0.0e+00
D2-D1    6.365  3.597488  9.132512 2.7e-06

diff: the difference between the two groups’ means lwr, upr: bottom and upper-end points of the confidence interval at 95 percent (default) p adj: p-value after multiple comparisons adjustment

The output shows that all pairwise comparisons with an adjusted p-value of 0.05 are significant.

Using the multcomp package, perform several comparisons.

The function glht() [in the multcomp package] can be used to do multiple comparison processes for an ANOVA. General linear hypothesis tests are abbreviated as glht. The following is a simplified format:

summary(glht(res.aov2, linfct = mcp(dose = "Tukey")))

     Simultaneous Tests for General Linear Hypotheses

Multiple Comparisons of Means: Tukey Contrasts
Fit: aov(formula = len ~ supp + dose, data = my_data)Linear Hypotheses:
               Estimate Std. Error t value Pr(>|t|)   
D1 - D0.5 == 0    9.130      1.210   7.543   <1e-05 ***
D2 - D0.5 == 0   15.495      1.210  12.802   <1e-05 ***
D2 - D1 == 0      6.365      1.210   5.259   <1e-05 ***
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Adjusted p values reported -- single-step method)

T-test on pairs

Pairwise comparisons between group levels with various testing corrections can also be calculated using the pairwise.t.test() function.

pairwise.t.test(data$len, data$dose,
                p.adjust.method = "BH")

    Pairwise comparisons using t-tests with pooled SD

data:  data$len and data$dose
   D0.5    D1    
D1 1.0e-08 -     
D2 4.4e-16 1.4e-05
Method for adjusting the P value: BH

The validity of ANOVA assumptions should be checked.

The data must be regularly distributed, and the variation between groups must be homogeneous. With certain diagnostic plots, we can verify this.

Examine the assumption of homogeneity of variance.

The residuals versus fits graphic are used to assess for variance homogeneity. There are no obvious correlations between residuals and fitted values (the mean of each group) in the plot below, which is good. As a result, we can assume that the variances are homogeneous.

1. Homogeneity of variances

plot(res.aov3, 1)

Two-Way ANOVA Test in R

Outliers are found at points 32 and 23, which can have a significant impact on normalcy and variance homogeneity. To meet the test assumptions, it can be beneficial to remove outliers.

Check the homogeneity of variances with Levene’s test. The leveneTest() method [from the car package] will be used:

leveneTest(len ~ supp*dose, data = data)

Levene’s Test for Homogeneity of Variance (center = median)

      Df F value Pr(>F)
group  5  1.7086 0.1484

The p-value is not less than the significance level of 0.05, as seen in the output above. This indicates that there is no indication that the variance across groups is statistically significant.

As a result, we can infer that the variations in the different treatment groups are homogeneous.

Examine the assumption of normality.

The residuals’ normality plot. The residuals quantiles are displayed against the normal distribution quantiles in the graph below. Also plotted is a 45-degree reference line.

The residuals’ normal probability plot is used to confirm that the residuals are normally distributed.

The residuals’ normal probability plot should roughly follow a straight line.

2. Normality

plot(res.aov3, 2)

We can infer normalcy because all of the points lie roughly along this reference line.

The Shapiro-Wilk test on the ANOVA residuals (W = 0.98, p = 0.5), which finds no evidence of normality violation, supports the previous conclusion.

Extract the residuals

aov_residuals <- residuals(object = res.aov3)

Run Shapiro-Wilk test

shapiro.test(x = aov_residuals )

    Shapiro-Wilk normality test

data:  aov_residuals
W = 0.98499, p-value = 0.6694

In R, create a two-way ANOVA test for unbalanced designs.

In an unbalanced design, each group has an unequal number of participants.

In an imbalanced design, there are three essentially distinct approaches to do an ANOVA. Type-I, Type-II, and Type-III sums of squares are the three types.

To keep things simple, the Type-III sums of squares are the recommended method.

When the design is balanced, all three ways produce the same result. When the design is unbalanced, however, they do not produce the same outcomes.

For unbalanced designs, the function Anova() [in the car package] can be used to compute a two-way ANOVA test.

Install the software on your PC first. Type install.packages(“car”) in R. Then:

my_anova <- aov(len ~ supp * dose, data = data)
Anova(my_anova, type = "III")
Anova Table (Type III tests)
Response: len
             Sum Sq Df F value    Pr(>F)   
(Intercept) 1750.33  1 132.730 3.603e-16 ***
supp         137.81  1  10.450  0.002092 **
dose         885.26  2  33.565 3.363e-10 ***
supp:dose    108.32  2   4.107  0.021860 * 
Residuals    712.11 54             
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

That’s all from this post…Keep in touch with us.

The post Two-Way ANOVA Example in R-Quick Guide appeared first on

To leave a comment for the author, please follow the link and comment on their blog: offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)