Activity 09 — T-Tests I: Setting Up the Test

Hypotheses and checking assumptions before we test

tidyverse
descriptive-stats

Hands-on companion to T-Tests I: state hypotheses, check normality (histograms, box plot + QQ plot, Shapiro-Wilk) and variance (Levene’s), then run Welch’s t-test. Students hand in the same analysis, written themselves, on sugar maple leaves.

Author

Bill Perry

Published

October 1, 2026

In-class Activity 9: Setting Up the Test

Today’s Objectives

  1. State null and alternate hypotheses before running any test
  2. Check normality — stacked histograms, a box plot next to a QQ plot, and the Shapiro-Wilk test
  3. Check equal variance — Levene’s test
  4. Run Welch’s t-test (we decode the output next lecture)
Tip📂 Files for today
ImportantWhat you hand in

The skeleton has two parts:

  • Part A — in class (leaf data). The code is already written. Run it chunk by chunk as you work through Parts 1–8 below, and type your answers in the QA boxes.
  • Part B — hand in (sugar maple data). Only the questions are there. You write the code for the assumption checks by copying the matching Part A chunk and changing what needs changing. Answer the QB boxes. (The sugar maple t-tests come next lecture.) See Part 9.

Answer boxes look like this — type after ANSWER: and start every line with #:

# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# QA2. How far apart are the two means?
# ANSWER: shady is about 0.02 g heavier
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #

Keep the plots plain today — no colors, labels, or themes needed. The goal is to read them, not polish them.


🧩 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

▶ Run this (already at the top of your skeleton — it loads both data sets):

# Load libraries ---------------------------------------
library(readxl)      # read Excel files
library(tidyverse)   # dplyr + ggplot2
library(janitor)     # clean_names()
library(car)         # Levene's test

# Load the leaf data (Part A) --------------------------
leaf_df <-
  read_excel("data/2026_09_03_data_sci_leaf_area.xlsx") %>%
  clean_names()

# Load the sugar maple data (Part B) -------------------
maple_df <-
  read_excel("data/2026_09_02_sugar_maple_leaf_area.xlsx") %>%
  clean_names()

glimpse(leaf_df)
glimpse(maple_df)

⚠️ Watch out! If R says could not find function "leveneTest", you haven’t loaded library(car). Install it once with install.packages("car").


Part 2 · Quick descriptive stats

🔮 Predict first: guess the mean leaf mass for each side and which side has more spread (bigger SD).

▶ Run this:

# Mean and SD of leaf mass for each side ---------------
leaf_df %>%
  group_by(shade) %>%
  summarize(
    n    = sum(!is.na(mass_g)),
    mean = mean(mass_g, na.rm = TRUE),
    sd   = sd(mass_g, na.rm = TRUE)
  )

✏️ QA2: How far apart are the two means? Which side has the bigger SD?


🧩 Chunk 2 — Hypotheses & assumptions (after lecture Chunk 2)

Parts 3–7: state your hypotheses, then check normality and variance — before you test.

Part 3 · State your hypotheses FIRST

✏️ QA3: Write H₀ and Hₐ for leaf mass, in words and in symbols (use μ for “mean”). Say whether the test is one- or two-tailed, and what α is. There is no code for this part — just the answer box.


Part 4 · Normality — stacked histograms

facet_wrap(~shade, ncol = 1) stacks the two histograms on top of each other so they share one x-axis — you can compare where each group sits and how wide it spreads.

▶ Run this:

# Histograms of leaf mass, one on top of the other ------
leaf_df %>%
  ggplot(aes(x = mass_g)) +
  geom_histogram(binwidth = 0.05) +
  facet_wrap(~shade, ncol = 1)

✏️ QA4: For each side — roughly bell-shaped, or lopsided with a long tail? Which side spreads wider?


Part 5 · Normality — box plot and QQ plot

A QQ plot is new, so start with something you know.

Step 1: the box plot

▶ Run this:

# Box plot of leaf mass by side ------------------------
leaf_df %>%
  ggplot(aes(x = shade, y = mass_g)) +
  geom_boxplot()

The box plot shows three quantiles — values that a given percent of the leaves fall below:

Part of the box Quantile Meaning
Bottom of box Q1 25% of leaves are lighter
Middle line Median 50% of leaves are lighter
Top of box Q3 75% of leaves are lighter

In a normal (bell-shaped) distribution the median sits in the middle of the box and the two whiskers are about the same length. A long whisker on one side = a long tail on that side.

Step 2: the QQ plot

A QQ plot uses every leaf as a quantile, not just three. R:

  1. sorts the leaves from lightest to heaviest (the y-axis),
  2. works out where each leaf would be if the data were perfectly normal (the x-axis),
  3. plots one dot per leaf.

If the data are normal, the dots fall on a straight line.

🔮 Predict first: From the box plot, which side’s dots do you expect to bend away from the line — and at the top or the bottom?

▶ Run this:

# QQ plot of leaf mass for each side --------------------
leaf_df %>%
  ggplot(aes(sample = mass_g)) +
  stat_qq() +
  stat_qq_line() +
  facet_wrap(~shade)
  • A QQ plot uses aes(sample = ...) — not x = or y =.
  • stat_qq() draws the dots; stat_qq_line() draws the “perfectly normal” line.

How to read it:

What the dots do What it means
Follow the line Normal — good
Curve up at the right end Long tail of big values (right-skewed)
Curve down at the left end Long tail of small values (left-skewed)
One or two dots far off at an end Outliers

A little wiggle at the very ends is normal. Look for a clear bend.

✏️ QA5: For each side, do the dots follow the line? Where do they bend away? Does that match the long whisker in the box plot and the tail in the histogram?


Part 6 · Normality — Shapiro-Wilk test

The plots are your main evidence. Shapiro-Wilk puts a p-value on it.

  • H₀: the data are normal
  • p > 0.05 → no evidence against normality
  • p < 0.05 → evidence the data are not normal

🔮 Predict first: From your QQ plots, will p be above or below 0.05 for each side?

▶ Run this:

# Shapiro-Wilk p-value for each side -------------------
leaf_df %>%
  group_by(shade) %>%
  summarize(shapiro_p = shapiro.test(mass_g)$p.value)

This is the same group_by() + summarize() you use for means — it runs the test once per side. $p.value pulls just the p-value out of the test result.

✏️ QA6: Record p for each side. Does the test agree with your QQ plots?

⚠️ Watch out! A p just under 0.05 with a QQ plot that is mostly on the line is a mild problem, not a disaster — the t-test handles small departures from normality.


Part 7 · Equal variance — Levene’s test

  • H₀: the two groups have equal variance (spread)
  • p > 0.05 → spreads not significantly different
  • p < 0.05 → spreads differ

🔮 Predict first: From your SDs in Part 2 and the stacked histograms, will p be above or below 0.05?

▶ Run this:

# Levene's test for equal variance ---------------------
leveneTest(mass_g ~ shade, data = leaf_df)

✏️ QA7: Record F and p. We use Welch’s t-test either way — why is it the safe choice?


🧩 Chunk 3 — Run the test (after lecture Chunk 3)

Part 8 · Run Welch’s t-test

🔮 Predict first: above or below 0.05?

▶ Run this:

# Welch's two-sample t-test ----------------------------
leaf_model <- t.test(mass_g ~ shade, data = leaf_df,
                     var.equal = FALSE)
leaf_model

✏️ QA8: Record t, df, and the p-value. Do you reject H₀ or fail to reject it? We go through every line of this output next lecture.

Save your script. That’s Part A done — on to Part B.


Part 9 · Your turn: sugar maple (hand in)

Start in class, finish before next class. This is Part B of your skeleton and it is what gets graded. You are checking the assumptions only — the sugar maple t-tests (two-sample and paired) are T-Tests II.

Every step in Part B matches a step in Part A. For each # TODO:, copy the matching Part A chunk, paste it under the TODO, and change what needs changing. Things that are different:

  • the data frame is maple_df
  • the groups are in a column called side (not shade)
  • sugar maple leaves are much heavier (about 2–5 g) — a binwidth of 0.05 will give you a comb of skinny bars; pick a better one
  • B8 changes the variable too: repeat the checks for leaf area, total_area_cm2
  • B9 looks at the design — how many leaves came from each tree and side
  • B10 is your decision: based on your checks, is a t-test OK for mass and for area?

Give every chunk its own # comment, and answer every QB box.

⚠️ Watch out! If you get object 'shade' not found, you changed the data frame but not the group column.

When you’re done, run the whole script top to bottom (Ctrl/Cmd + Shift + Enter) — it must run with no errors — and hand in scripts/09_t_tests_1.R.


End of Activity 09. Next: Activity 10 — T-Tests II: reading the output, writing a results sentence, and paired t-tests.