Activity 09 — T-Tests I: Setting Up the Test
Hypotheses, the t-statistic, and checking assumptions before we test
Hands-on companion to T-Tests I: state hypotheses, check normality and variance, and see where the t-statistic comes from.
In-class Activity 9: Setting Up the Test
Recap from Activities 05–06
- Loaded the leaf data with
read_excel()andclean_names()intoleaf_df - Used
group_by()+summarize()to compute mean, SD, and SE per group - Checked for missing values with
sum(!is.na()) - Made boxplots with jittered points and mean ± SE plots with
stat_summary()(Activity 03) - Prediction: shady-side leaves would be heavier than sunny-side leaves
Today’s Objectives
- See where the t-statistic comes from and why we default to Welch’s
- State null and alternate hypotheses formally before running any test
- Check the normality assumption — histogram and Shapiro-Wilk test
- Check the variance assumption — Levene’s test
How this activity works
You are building one R script this whole class, and you turn it in. Create it now:
scripts/09_t_tests_1.R.
- Every line you run goes in that script — not the Console, not this page. Type it, don’t paste it.
- Start every code chunk in your script with a short
#comment saying what it does. The comments are part of the grade.- The top of your script, in order: a title comment, then your
library()calls, then the line that loads the data intoleaf_df.- Code marked ▶ Run this is typed into your script exactly as shown. Code marked ✏️ Your turn is a change you make and run.
- The Going further section is optional if you finish early.
🔮 Predict before you run
Before you run any ▶ Run this block, cover the output and predict what R will print. A t-test lives or dies on the p-value — committing to “above or below 0.05” before you see the real number is what forces you to reason about the test instead of skimming the output.
🧩 Chunk 1 — Setup & recap (after lecture Chunk 1)
Parts 1–2: load the data and recompute the descriptive stats.
Part 1 · Load libraries and data
Same leaf file you have used since Activity 02 — it should already be in your project’s data/ folder. If not: 2026_09_03_data_sci_leaf_area.xlsx → put it in data/.
▶ Type this at the top of scripts/09_t_tests_1.R — in this exact order:
# ---- Activity 09: T-Tests I -----------------------------
# your name, today's date
# ---- Libraries -----------------------------------------
library(readxl) # read Excel files
library(tidyverse) # dplyr + ggplot2
library(janitor) # clean_names()
library(car) # Levene's test for variance⚠️ Watch out! If R says
"could not find function 'leveneTest'"you forgotlibrary(car). Install it once withinstall.packages("car"), then load it every session.
# ---- Load data ----------------------------------------
leaf_df <- read_excel("data/2026_09_03_data_sci_leaf_area.xlsx") %>%
clean_names()
glimpse(leaf_df)✏️ Your turn: From the glimpse() output, fill in:
Number of rows:
Column holding the grouping variable (sunny/shady):
Column we are testing today (leaf mass):
Part 2 · Quick descriptive stats review
🔮 Predict first: Before running, guess the mean mass for each side (shady vs sunny) and the gap between them. Write your guesses, then check.
▶ Run this:
# Recap stats from Activity 06 — both groups at once ----
recap_df <- leaf_df %>%
group_by(shade) %>%
summarize(
n = sum(!is.na(mass_g)),
mean_mass = round(mean(mass_g, na.rm = TRUE), 3),
sd_mass = round(sd(mass_g, na.rm = TRUE), 3),
se_mass = round(sd_mass / sqrt(n), 3)
)
recap_df✏️ Your turn: Record the values you will need for the t-test formula:
Shady: mean = _____ g SD = _____ n = _____
Sunny: mean = _____ g SD = _____ n = _____
Difference in means = _____ g
✏️ Your turn: Looking at the SD values, do the two groups appear to have similar or different variance? (Does one group have much more spread than the other?)
Your observation:
🧩 Chunk 2 — Hypotheses & assumptions (after lecture Chunk 2)
Parts 3–6: state your hypotheses, then check normality and variance — before you test.
Part 3 · State your hypotheses FIRST
💡 Key idea: Always write down your hypotheses before you look at the data or run any test. This is how you stay scientifically honest.
✏️ Your turn: Write the null and alternate hypotheses for leaf mass. Use words first, then symbols.
In words:
H₀ (null hypothesis):
Hₐ (alternate hypothesis):
In symbols (use μ for "mean"):
H₀:
Hₐ:
Test type (circle one): one-tailed / two-tailed
Significance level α:
✏️ Your turn: Why do we use a two-tailed test here even though we predicted shady leaves would be heavier?
Your answer:
Part 4 · Check normality — histogram
The t-test assumes that data within each group are approximately normally distributed.
▶ Run this:
# Histogram per group — look for a bell-shaped distribution
hist_norm_plot <- leaf_df %>%
ggplot(aes(x = mass_g, fill = shade)) +
geom_histogram(binwidth = 0.05, color = "white", alpha = 0.8) +
facet_wrap(~shade, ncol = 2) +
labs(
title = "Leaf Mass Distribution by Shade",
x = "Leaf Mass (g)",
y = "Count"
) +
theme_minimal() +
theme(legend.position = "none")
hist_norm_plot✏️ Your turn: Describe the shape of each histogram:
Sunny side shape (bell-shaped, skewed, flat, bimodal?):
Shady side shape:
Any obvious outliers? Y / N
Part 5 · Check normality — Shapiro-Wilk test
🔮 Predict first: Shapiro-Wilk’s H₀ is “data are normal.” From your histogram, predict whether p will be above or below 0.05 for each side before you run it.
- H₀ (Shapiro-Wilk): the data are normally distributed
- p > 0.05 → fail to reject normality → proceed with t-test
- p < 0.05 → evidence of non-normality → consider alternatives
▶ Run this:
# Shapiro-Wilk for the sunny side --------------------
leaf_df %>%
filter(shade == "sunny") %>%
pull(mass_g) %>%
shapiro.test()# Shapiro-Wilk for the shady side -------------------
leaf_df %>%
filter(shade == "shady") %>%
pull(mass_g) %>%
shapiro.test()✏️ Your turn: Record the results and state your decision:
Sunny: W = _____ p = _____ Decision (normal / not normal):
Shady: W = _____ p = _____ Decision (normal / not normal):
⚠️ Watch out! One side may come back p < 0.05 while the other does not. A borderline Shapiro result with a roughly bell-shaped histogram is not a reason to abandon the t-test — the test is fairly robust to mild non-normality. Look at the histogram and the test together.
Part 6 · Check variance — Levene’s test
🔮 Predict first: Look back at the two SD values from Part 2. Predict whether Levene’s will call the variances equal (p > 0.05) or not, before you run it.
- H₀ (Levene’s): the two groups have equal variance
- p > 0.05 → variances are not significantly different
- p < 0.05 → variances are significantly different
▶ Run this:
# Levene's test for equal variance -------------------
leveneTest(mass_g ~ shade, data = leaf_df)✏️ Your turn: Record the result and decide:
F-statistic = _____ p = _____
Decision (variances equal / not equal):
✏️ Your turn: We are going to use Welch’s t-test (var.equal = FALSE) regardless of Levene’s result. In your own words, why is Welch’s the safe default even when Levene’s says variances are equal?
Your answer:
Part 7 · Review and checkpoint
At this point you should be able to:
✏️ Your turn: Run your entire script from top to bottom with Ctrl/Cmd + Shift + Enter (Source). Does it run without errors?
Ran cleanly? Y / N
If not, what error appeared:
Part 8 · Going further
Optional — work through it if you finish early. Next lecture assumes you’ve completed Parts 1–7 only, not this section.
An alternative normality check: the QQ plot
A QQ (quantile-quantile) plot compares your data’s quantiles to what a perfect normal distribution would look like. Points on the diagonal line = normal.
▶ Try this:
# Q-Q plot — points should fall along the diagonal ----
qq_norm_plot <- leaf_df %>%
ggplot(aes(sample = mass_g, color = shade)) +
stat_qq() +
stat_qq_line(color = "black", linewidth = 0.8) +
facet_wrap(~shade, scales = "free") +
labs(
title = "Normal Q-Q Plots by Shade",
x = "Theoretical Quantiles",
y = "Sample Quantiles"
) +
theme_minimal() +
theme(legend.position = "none")
qq_norm_plotDo the points for each group fall approximately along the diagonal?
Does this agree with the histogram and Shapiro-Wilk?
Calculate t by hand
The lecture showed you where the t-statistic comes from. Try reproducing it yourself:
# Reproduce the t-statistic manually -----------------
m_sha <- recap_df$mean_mass[recap_df$shade == "shady"]
m_sun <- recap_df$mean_mass[recap_df$shade == "sunny"]
s_sha <- recap_df$sd_mass[recap_df$shade == "shady"]
s_sun <- recap_df$sd_mass[recap_df$shade == "sunny"]
n_sha <- recap_df$n[recap_df$shade == "shady"]
n_sun <- recap_df$n[recap_df$shade == "sunny"]
# Welch's SE of the difference
se_diff <- sqrt((s_sha^2 / n_sha) + (s_sun^2 / n_sun))
# t-statistic
t_manual <- (m_sha - m_sun) / se_diff
cat("Difference in means:", round(m_sha - m_sun, 3), "g\n")
cat("SE of difference: ", round(se_diff, 4), "\n")
cat("Manual t: ", round(t_manual, 3), "\n")Manual t = _____ (write it down — you'll compare to t.test() next lecture)
Getting unstuck
- Read the error message out loud. R usually names the line and the problem.
- Check the usual suspects: loaded
library(car),library(readxl),library(tidyverse),library(janitor)? Column spelling? Checknames(leaf_df). leveneTestnot found? You needlibrary(car).- Shapiro-Wilk needs > 3 values. If you filtered to a tiny subset, you may get an error.
- Cheat sheets — https://posit.co/resources/cheatsheets/
- Bring the exact error (copy-paste it) to class, Canvas, or office hours.
End of Activity 09. Next: Activity 10 — T-Tests II: running the test, decoding the output, and writing a results sentence.