11.1 Statistics: Tests
Plot first. Save t.test() and aov(). The p-value is evidence against H0.
1. Question, then evidence
A test is the last step, not the first. A useful order:
- a question you can say in words
- EDA (a plot, a table)
- a model that matches the question
- a test saved as an object
- a sentence a reader can use
Always assign the test. Printing it once is not enough if you later want the p-value or the interval.
library(tidyverse)
library(palmerpenguins)
tt <- t.test(mpg ~ factor(am), data = mtcars)
tt
##
## Welch Two Sample t-test
##
## data: mpg by factor(am)
## t = -3.7671, df = 18.332, p-value = 0.001374
## alternative hypothesis: true difference in means between group 0 and group 1 is not equal to 0
## 95 percent confidence interval:
## -11.280194 -3.209684
## sample estimates:
## mean in group 0 mean in group 1
## 17.14737 24.39231
tt$p.value
## [1] 0.001373638
tt$estimate
## mean in group 0 mean in group 1
## 17.14737 24.39231
Sleuth3 has the classic two-sample tables from The
Statistical Sleuth. Install once in the console, then load a table
by name:
install.packages("Sleuth3")
data("case0201", package = "Sleuth3")
case0201 is wide: Cross and
Self are two numeric columns.
t.test(Cross, Self) is the two-vector form. The formula
form needs a long table. If Sleuth3 is not installed, use
palmerpenguins or mtcars. Those are the
fallbacks for the rest of this note.
2. Two-sample t.test()
Plot first. A boxplot of the numeric variable by the two-level group tells you whether the question is even interesting.
ggplot(mtcars, aes(x = factor(am), y = mpg)) +
geom_boxplot() +
theme_bw()

Then the test. The formula is y ~ group. The grouping
variable should have exactly two levels.
tt <- t.test(mpg ~ factor(am), data = mtcars)
species in penguins has three levels.
Filter to a pair, then droplevels() so the unused level is
gone. Without that, t.test() stops.
penguins2 <- penguins |>
filter(species %in% c("Adelie", "Gentoo")) |>
droplevels()
ggplot(penguins2, aes(x = species, y = bill_length_mm)) +
geom_boxplot() +
theme_bw()

tt_bill <- t.test(bill_length_mm ~ species, data = penguins2)
case0201 after a pivot is the same pattern:
case0201 |>
pivot_longer(everything(), names_to = "type", values_to = "height") |>
t.test(height ~ type, data = _)
## Error:
## ! object 'case0201' not found
Default t.test() does not assume equal variances
(Welch). That is the usual choice.
3. H0 and the p-value
State H0 in words before you look at the number.
For the penguin pair: mean bill length is the same for Adelie and
Gentoo. For mtcars: mean mpg is the
same for the two transmission codes.
The p-value is evidence against that H0. A small
p-value means the data would be surprising if H0 were true. It is not a
stamp that the result is “real,” and 0.05 is not a magic
cutoff. Report the p-value, the estimates, and the interval. Then write
the sentence.
4. ANOVA and nested models
Three or more groups: aov(y ~ factor), then
summary(). Save the object.
fit_aov <- aov(bill_length_mm ~ species, data = penguins)
summary(fit_aov)
## Df Sum Sq Mean Sq F value Pr(>F)
## species 2 7194 3597 410.6 <2e-16 ***
## Residuals 339 2970 9
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 2 observations deleted due to missingness
H0 in words: mean bill length is the same for Adelie, Chinstrap,
and Gentoo. iris (Sepal.Length ~ Species)
and mtcars (mpg ~ factor(cyl)) are the
no-package backups. Sleuth3 case0501 is
Lifetime ~ Treatment if you have the package.
anova() compares nested models. The
smaller model is a special case of the larger one.
m1 <- lm(mpg ~ wt, data = mtcars)
m2 <- lm(mpg ~ wt + hp, data = mtcars)
cmp <- anova(m1, m2)
cmp
## Analysis of Variance Table
##
## Model 1: mpg ~ wt
## Model 2: mpg ~ wt + hp
## Res.Df RSS Df Sum of Sq F Pr(>F)
## 1 30 278.32
## 2 29 195.05 1 83.274 12.381 0.001451 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
H0: once wt is in the model, hp does
not improve the fit. Same rule: save cmp, read the
p-value as evidence against that H0, not as a 0.05 switch.
aov() is the one-factor (or multi-factor) fit.
anova() is the comparison. Do not mix the two names by
accident.
5. Practice
- Boxplot and two-sample
t.test()for a numeric variable by a two-level group. Usempg ~ factor(am)inmtcars, or Adelie vs Gentoo bill length inpenguins(filter, thendroplevels()). If you haveSleuth3, docase0201as well. State H0 in words. Save the test object. aov()of a numeric variable by a factor with three or more levels (bill_length_mm ~ species, ormpg ~ factor(cyl), orSepal.Length ~ Species). Save the fit. State H0 in words.