Activity: T-Tests: One, Two, Paired

Comparing means

Hands-on worksheet: exploratory plots, then one-sample (two- and one-tailed), two-sample, and paired t-tests on the pine needle data the class collected — with questions, predictions, hypotheses, degrees of freedom, and interpretation written out by hand.
Author

Bill Perry

Worksheet: T-Tests — One, Two, Paired

📥 Data (.csv) 📥 Data (.xlsx) 📥 Tools script (.R) 📥 Script skeleton (.R)

ImportantColumn names in pine_data.csv / pine_data.xlsx

Both files hold the same six columns, with exactly these names. Type them exactly as written — R is case-sensitive, and Needle_Length or needle_length will not work:

Column What it is
source_file the raw team file the row came from
team field team — one team = one tree
replicate needle number within a tree side (1-8)
side sun exposure — "shady" or "sunny"
needle_length_mm needle length, millimetres
needle_width_mm needle width, millimetres

After you read the file in, click pine_df in the Variables pane in Positron and check the names on your screen against this table before you write any other code.


Section 1 · Read In the Data and Look at It

1.1 · Setup

Download the tools script from the button at the top. In your project folder, make a new folder called themes_functions and save the file into it, so the path is themes_functions/r_themes_and_functions.R.

That is a different folder from scripts/ — scripts/ holds the code you write, themes_functions/ holds tools you source() and do not edit.

One source() line then gives you two things you will use for the rest of the semester:

  • theme_regular() — the publication theme from the Graphing lecture
  • summary_stats() — hand it a column of numbers, get back n / mean / sd / se and a 95% CI

▶ Run this:

# ── Part 1.1 · Setup ────────────────────────────────────────────────────
library(tidyverse)
library(janitor)   # round_half_up(), used inside summary_stats()
library(car)       # leveneTest()

source("themes_functions/r_themes_and_functions.R")

pine_df <- read_csv("data/pine_data.csv")

⚠️ Watch out! source() needs the path to be right. If R says cannot open file, that is exactly what it is telling you — check that the folder is called themes_functions, spelled exactly that way, and that it sits next to your .Rproj file.

💡 You do not need glimpse() or head(). Click pine_df in the Variables pane in Positron and look at it there.

1.2 · Look at all the data — spread

Before we do anything clever, look at every needle we measured — all 64 rows, exactly as they came out of the field.

▶ Run this:

# ── Part 1.2 · Boxplot of all the raw needles ───────────────────────────
raw_box_plot <- pine_df %>%
  ggplot(aes(side, needle_length_mm)) +
  geom_boxplot() +
  geom_point(position = position_jitter(width = 0.08)) +
  theme_regular(base_size = 10)

raw_box_plot

💡 Why position_jitter() here? 32 needles land on a single x value, so a plain geom_point() would stack them into a line and hide the duplicates. Jitter nudges them sideways so you can see how many there are.

✏️ Your turn: How many points are on this plot, and how many trees did we actually climb?

points = ________        trees = ________

1.3 · Look at all the data — mean and standard error

▶ Run this:

# ── Part 1.3 · Mean and SE of all the raw needles ───────────────────────
raw_mean_se_plot <- pine_df %>%
  ggplot(aes(side, needle_length_mm)) +
  stat_summary(fun = mean, geom = "point") +
  stat_summary(fun.data = mean_se, geom = "errorbar", width = 0.2) +
  theme_regular(base_size = 10)

raw_mean_se_plot

✏️ Your turn: Do these two error bars overlap? Just from this picture, would you expect a t-test to find a difference?

____________________________________________________________________

____________________________________________________________________

⚠️ Watch out — those error bars are a lie. They are computed as SD / √n with n = 32 per side. But we did not measure 32 independent things. We measured 4 trees, eight needles each. The 32 is fake, so the precision is fake. Hold this picture in your head — we fix it next and then plot it again.

1.4 · Average out the pseudoreplication

The 8 needles from one side of one tree are pseudoreplicates — they are not 8 independent measurements, they are 8 looks at the same tree side. Average them so each tree side is one data point.

▶ Run this:

# ── Part 1.4 · Average out pseudoreplication ────────────────────────────
p_df <- pine_df %>%
  group_by(team, side) %>%
  summarise(needle_length_mm = mean(needle_length_mm, na.rm = TRUE),
            .groups = "drop")

p_df

⚠️ Watch out! 64 rows became 8. That is not data loss — it is the correct sample size. We measured 8 needles per side, but only 4 trees.

✏️ Your turn: What is our true sample unit here — needles? branches? trees? tree sides? Why does that matter for the rest of this worksheet?

____________________________________________________________________

____________________________________________________________________

____________________________________________________________________

1.5 · Summary statistics, the long way

▶ Run this:

# ── Part 1.5 · Summary stats the long way ───────────────────────────────
stats_df <- p_df %>%
  group_by(side) %>%
  summarize(
    n    = sum(!is.na(needle_length_mm)),
    mean = mean(needle_length_mm, na.rm = TRUE),
    sd   = sd(needle_length_mm, na.rm = TRUE),
    se   = sd / sqrt(n),
    .groups = "drop"
  )

stats_df

1.6 · Summary statistics, the short way

The exact same numbers, from the function you source()d in Part 1.1:

▶ Run this:

# ── Part 1.6 · Summary stats with summary_stats() ───────────────────────
summary_stats(p_df$needle_length_mm)                 # all 8 trees together

p_df %>% group_by(side) %>% reframe(summary_stats(needle_length_mm))

💡 Why reframe()? summarize() wants one value per group. summary_stats() hands back a whole row — six columns at once — so reframe() is the verb that allows it. The group_by() in front is the same one you already know.

✏️ Your turn: Write down n, mean, sd and se for each side, and check them against stats_df.

shady:  n = ______   mean = ______   sd = ______   se = ______

sunny:  n = ______   mean = ______   sd = ______   se = ______

Do they match stats_df?  ______________________________________

✏️ Your turn: summary_stats() also gives you ci_lower and ci_upper — a 95% confidence interval. Look at the n column. How many degrees of freedom did it use to build that interval, and why that number?

____________________________________________________________________

____________________________________________________________________

1.7 · The same two plots, after averaging

Exactly the same two plots as Parts 1.2 and 1.3 — the only thing that changed is pine_df became p_df.

▶ Run this:

# ── Part 1.7 · Boxplot and mean SE of the tree means ────────────────────
box_plot <- p_df %>%
  ggplot(aes(side, needle_length_mm)) +
  geom_boxplot() +
  geom_point() +
  theme_regular(base_size = 10)

box_plot

mean_se_plot <- p_df %>%
  ggplot(aes(side, needle_length_mm)) +
  stat_summary(fun = mean, geom = "point") +
  stat_summary(fun.data = mean_se, geom = "errorbar", width = 0.2) +
  theme_regular(base_size = 10)

mean_se_plot

💡 No jitter needed now. Four points per side, so plain geom_point() is enough.

✏️ Your turn: Fill this in from your Part 1.3 and Part 1.6 output. This is the most important table in the worksheet.

                    raw needles        tree means

n per side          __________         __________

shady mean          __________         __________

sunny mean          __________         __________

shady se            __________         __________

sunny se            __________         __________

✏️ Your turn: What happened to the means when you averaged? What happened to the standard errors?

____________________________________________________________________

____________________________________________________________________

✏️ Your turn: Compare raw_mean_se_plot to mean_se_plot side by side on your screen. Which one looks like stronger evidence? Which one is honest? Explain in one sentence why the raw version was not actually more precise.

____________________________________________________________________

____________________________________________________________________

____________________________________________________________________

1.8 · One line per tree

▶ Run this:

# ── Part 1.8 · One line per tree ────────────────────────────────────────
slope_plot <- p_df %>%
  ggplot(aes(side, needle_length_mm, group = team)) +
  geom_line() +
  geom_point() +
  theme_regular(base_size = 10)

slope_plot

✏️ Your turn: The boxes in Part 1.7 overlapped a lot. Now look at the lines. Do they all slope the same way? Write down what you see — this is the whole point of Section 4.

____________________________________________________________________

____________________________________________________________________

ImportantDegrees of freedom — read this before Sections 2, 3 and 4

df = the number of values free to vary once you have estimated a mean. You lose one df for every mean you estimate. Our data is 4 trees, one shady side and one sunny side each — so n is always trees, never needles.

Test What n counts df formula Our df
One-sample (Section 2) trees in the one group n − 1 3
Two-sample (Section 3) trees in both groups n₁ + n₂ − 2 6
Welch’s (Section 3) both groups, penalised Welch–Satterthwaite ≈ 5.66
Paired (Section 4) differences npairs − 1 3

Check the df = line in every bit of t.test() output below against this table. If it does not match, you ran a different test than you thought you did.


Section 2 · One-Sample t-Test

A one-sample t-test compares one group’s mean to a fixed number — not to another group. Here: is the mean needle length on the shady side different from the 15 mm reported for this species?

2.1 · Just the shady side

▶ Run this:

# ── Part 2.1 · Just the shady side ──────────────────────────────────────
shady_df <- p_df %>% filter(side == "shady")

shady_df

2.2 · Question, prediction, hypotheses

✏️ Your turn:

Question (the biology, in plain English):

____________________________________________________________________

Prediction (what you expect, and WHY — the biology):

____________________________________________________________________

____________________________________________________________________

H0 (null hypothesis):

____________________________________________________________________

Ha (alternative hypothesis):

____________________________________________________________________

alpha = ____________

✏️ Your turn: How many trees are in this test, and how many means did you estimate? So what is the df?

n = ________    means estimated = ________    df = ________

2.3 · Check normality

▶ Run this:

# ── Part 2.3 · Normality of the shady side ──────────────────────────────
qqnorm(shady_df$needle_length_mm)
qqline(shady_df$needle_length_mm)

shapiro.test(shady_df$needle_length_mm)

✏️ Your turn: Why do we check the shady side on its own rather than p_df$needle_length_mm (both sides pooled)? What would pooling two groups with different means do to the shape of the distribution?

____________________________________________________________________

____________________________________________________________________

2.4 · Run the two-tailed test

▶ Run this:

# ── Part 2.4 · One-sample t-test, two-tailed ────────────────────────────
one_sample_model <- t.test(shady_df$needle_length_mm, mu = 15)

one_sample_model

✏️ Your turn: Copy the numbers out, and check the df against what you predicted in Part 2.2.

t = ________    df = ________    p-value = ________

sample mean = ________ mm       95% CI: ________ to ________

Does the df match your answer above?  ______________________________

✏️ Your turn: Does 15 mm fall inside or outside that confidence interval? How does that relate to your p-value?

____________________________________________________________________

____________________________________________________________________

2.5 · The one-tailed variation

Your prediction in Part 2.2 probably had a direction — shady needles are shorter than 15 mm. If you committed to that direction before collecting data, you can put all 5% of α into one tail.

✏️ Your turn: Write the one-tailed hypotheses.

H0: mu ______ 15

Ha: mu ______ 15

▶ Run this — alternative is the only thing that changes:

# ── Part 2.5 · One-sample t-test, one-tailed ────────────────────────────
one_tailed_model <- t.test(shady_df$needle_length_mm, mu = 15, alternative = "less")

one_tailed_model

✏️ Your turn: Fill in the comparison from your own output.

                 t            df        p-value

Two-tailed       ________     ______    ________

One-tailed       ________     ______    ________

✏️ Your turn: What happened to t? To df? To the p-value? (Look hard at the relationship between the two p-values.)

____________________________________________________________________

____________________________________________________________________

✏️ Your turn: Look at the confidence interval in the one-tailed output. What is the lower bound, and why does that make sense?

____________________________________________________________________

____________________________________________________________________

2.6 · Why the one-tailed p is smaller

▶ Run this — the critical values behind that change, at df = 3:

# ── Part 2.6 · Critical values, one vs two tailed ───────────────────────
qt(0.975, df = 3)   # two-tailed, 2.5% in each tail
qt(0.95,  df = 3)   # one-tailed, 5% in one tail

✏️ Your turn: Which cutoff is closer to zero? So which test is easier to get a significant result from, and why?

____________________________________________________________________

____________________________________________________________________

2.7 · Point it the wrong way on purpose

▶ Run this:

# ── Part 2.7 · The wrong tail ───────────────────────────────────────────
t.test(shady_df$needle_length_mm, mu = 15, alternative = "greater")

✏️ Your turn: Same data, same t. What is the p-value now? What does that tell you about when you are allowed to choose the tail?

p-value = ________

____________________________________________________________________

____________________________________________________________________

⚠️ Watch out! Choosing the tail after looking at your means is not a one-tailed test. It is a two-tailed test with the p-value secretly halved, and it is one of the most common ways people accidentally cheat. Pick the direction from the biology, before you collect data, or use two-tailed.

Tip

🚀 If you finish early: A one-tailed test is defensible when a result in the opposite direction would lead to the same action as no result at all — a drug that harms patients and a drug that does nothing are both “do not approve.” For our pine needles, is “sunny is longer” and “shady is longer” both interesting biology? So which should we use?

____________________________________________________________________

2.8 · Interpretation

✏️ Your turn:

Do you reject or fail to reject H0 at alpha = 0.05?

____________________________________________________________________

In biological terms, what does this say about shady-side needles?

____________________________________________________________________

____________________________________________________________________

✏️ Your turn: Write the result the way it would appear in a paper.

"A two-tailed, one-sample t-test at alpha = 0.05 showed ___________

____________________________________________________________________

____________________________________________________________________

t(______) = ________, p = ________."

Section 3 · Two-Sample t-Test

A two-sample t-test compares the means of two independent groups — here, needle length on the sunny side versus the shady side.

3.1 · Question, prediction, hypotheses

Write all five down before you run anything.

✏️ Your turn:

Question (the biology, in plain English):

____________________________________________________________________

Prediction (what you expect to see, and WHY — the biology):

____________________________________________________________________

____________________________________________________________________

H0 (null hypothesis):

____________________________________________________________________

Ha (alternative hypothesis):

____________________________________________________________________

alpha = ____________

✏️ Your turn: Your prediction probably had a direction (“sunny needles are longer”). Is your Hₐ directional too, or is it just “they differ”? Which one does t.test() run by default?

____________________________________________________________________

____________________________________________________________________

3.2 · Check normality within each group

▶ Run this:

# ── Part 3.2 · Normality by group ───────────────────────────────────────
p_df %>%
  group_by(side) %>%
  summarize(
    shapiro_p = shapiro.test(needle_length_mm)$p.value,
    .groups = "drop")

✏️ Your turn: What is the null hypothesis of the Shapiro-Wilk test? Do you want a significant or a non-significant result here?

H0 of Shapiro-Wilk: ________________________________________________

I want p to be:     ________________________________________________

My result:  shady p = ____________   sunny p = ____________

⚠️ Watch out! With n = 4, Shapiro-Wilk has almost no power — it will pass nearly anything. A high p-value here is not evidence that the data are normal; it is evidence that we do not have enough data to tell. Run it, report it, but lean on the design and the biology.

3.3 · Check equal variances

▶ Run this:

# ── Part 3.3 · Equal variances ──────────────────────────────────────────
leveneTest(needle_length_mm ~ factor(side), data = p_df)

✏️ Your turn: What is the null hypothesis of Levene’s test? Based on your p-value, should you use the standard (Student’s) t-test or Welch’s?

H0 of Levene's test: _______________________________________________

My p-value: ____________

Test I will use, and why: __________________________________________

____________________________________________________________________

3.4 · Run both versions

▶ Run this:

# ── Part 3.4 · Two-sample t-test ────────────────────────────────────────
two_sample_model <- t.test(needle_length_mm ~ side, data = p_df, var.equal = TRUE)

two_sample_model

welch_model <- t.test(needle_length_mm ~ side, data = p_df, var.equal = FALSE)

welch_model

✏️ Your turn: Copy the numbers out of the output by hand.

                      t          df         p-value

Standard (Student's)  ________   ________   ________

Welch's               ________   ________   ________

✏️ Your turn: Welch’s df is not a whole number, and it is smaller than the standard df. Why? What did Welch’s buy you in exchange?

____________________________________________________________________

____________________________________________________________________

3.5 · Interpretation

✏️ Your turn: Answer in full sentences.

Do you reject or fail to reject H0 at alpha = 0.05?

____________________________________________________________________

What does the p-value actually mean here? (Careful — it is a statement
about the data GIVEN H0, not about whether H0 is true.)

____________________________________________________________________

____________________________________________________________________

In biological terms, what does this result say about pine needles?

____________________________________________________________________

____________________________________________________________________

✏️ Your turn: Write the result the way it would appear in a paper. The number in parentheses after t is the degrees of freedom — always report it.

"A two-tailed, two-sample t-test at alpha = 0.05 showed __________

____________________________________________________________________

____________________________________________________________________

t(______) = ________, p = ________."
Tip

🚀 If you finish early — compute the t-statistic by hand and confirm it matches t.test():

\[t = \frac{\bar{x}_1 - \bar{x}_2}{S_p\sqrt{\frac{1}{n_1} + \frac{1}{n_2}}}\]

# ── Part 3.6 · Two-sample t by hand ─────────────────────────────────────
sunny_df <- p_df %>% filter(side == "sunny")   # shady_df is from Part 2.1

n1 <- length(shady_df$needle_length_mm)
n2 <- length(sunny_df$needle_length_mm)
s1 <- sd(shady_df$needle_length_mm)
s2 <- sd(sunny_df$needle_length_mm)

sp <- sqrt(((n1 - 1) * s1^2 + (n2 - 1) * s2^2) / (n1 + n2 - 2))

(mean(shady_df$needle_length_mm) - mean(sunny_df$needle_length_mm)) /
  (sp * sqrt(1/n1 + 1/n2))

Look at the denominator of sp: n1 + n2 - 2. That is the degrees of freedom. df is not a rule to memorise — it is whatever you divided by.


Section 4 · Paired t-Test

A paired t-test compares two measurements taken from the same subject — here, the sunny side and the shady side of the same tree. It uses the differences between pairs as the data.

4.1 · Question, prediction, hypotheses

Notice that H₀ and Hₐ are now about one number: the mean difference.

✏️ Your turn:

Question (the biology, in plain English):

____________________________________________________________________

Prediction (what you expect, and WHY):

____________________________________________________________________

____________________________________________________________________

H0 (in terms of the mean difference between the two sides of a tree):

____________________________________________________________________

Ha:

____________________________________________________________________

alpha = ____________

4.2 · Reshape and run the test

t.test() needs the two sides side by side, one row per tree.

▶ Run this:

# ── Part 4.2 · Paired t-test ────────────────────────────────────────────
p_wide_df <- p_df %>%
  pivot_wider(names_from = side, values_from = needle_length_mm)

p_wide_df

paired_model <- t.test(p_wide_df$shady, p_wide_df$sunny, paired = TRUE)

paired_model

⚠️ Watch out — order matters for the sign. t.test(shady, sunny, paired = TRUE) computes shady − sunny. Swap the two and t flips sign. The p-value is identical, but the direction you report is backwards. Always write down which way you subtracted.

✏️ Your turn: Copy the numbers out, and note the df.

t = ________    df = ________    p-value = ________

mean difference = ________ mm     (this is ________ minus ________)

4.3 · Same data, two different answers

▶ Run this:

# ── Part 4.3 · Compare the two tests ────────────────────────────────────
two_sample_model$p.value

paired_model$p.value

✏️ Your turn: Fill in the table from your own output.

Test           t            df        p-value

Two-sample     ________     ______    ________

Paired         ________     ______    ________

✏️ Your turn: Both tests used the same 8 numbers and both estimated the same difference in means. So why are the p-values so different? (Think about where tree-to-tree variation ends up in each test.)

____________________________________________________________________

____________________________________________________________________

____________________________________________________________________

✏️ Your turn: The paired test has half the degrees of freedom — 3 instead of 6. Fewer df makes significance harder to reach, not easier. So how did it still do better?

____________________________________________________________________

____________________________________________________________________

4.4 · A paired test is a one-sample test

Treat the shady side as each tree’s own control and compute one difference per tree.

▶ Run this:

# ── Part 4.4 · Paired = one-sample on the differences ───────────────────
diff_df <- p_wide_df %>%
  mutate(difference = sunny - shady)

diff_df

diff_model <- t.test(diff_df$difference, mu = 0)

diff_model

✏️ Your turn: Compare diff_model to paired_model. Write down the t, the df and the p-value for each. What do you notice?

paired_model:  t = ________   df = ______   p = ________

diff_model:    t = ________   df = ______   p = ________

What I notice: _____________________________________________________

✏️ Your turn: So what is paired = TRUE actually doing for you?

____________________________________________________________________

✏️ Your turn: This is the answer to the degrees of freedom question. The two-sample test on these same measurements had df = 6. This one has df = 3. Explain why, in terms of how many numbers diff_model is actually working with.

____________________________________________________________________

____________________________________________________________________

4.5 · Interpretation

✏️ Your turn:

Do you reject or fail to reject H0 at alpha = 0.05?

____________________________________________________________________

Which test — two-sample or paired — is the CORRECT one for how we
collected this data, and why?

____________________________________________________________________

____________________________________________________________________

In biological terms, what does the paired result say about pine needles?

____________________________________________________________________

____________________________________________________________________

✏️ Your turn: Write the result the way it would appear in a paper.

"A two-tailed, paired t-test at alpha = 0.05 showed _______________

____________________________________________________________________

____________________________________________________________________

t(______) = ________, p = ________."
Tip

🚀 If you finish early: The shady side “controls for” everything about that particular tree — its genetics, its soil, its water, its team’s measuring technique. Subtracting removes all of it at once. Which assumption from Section 3 does this design let you stop worrying about? And which variable’s normality should you now be checking?

____________________________________________________________________

Reflection Questions

  1. How does the t-distribution differ from the normal distribution, and why does that matter when you only have 4 trees?
  2. What assumptions must be met to use a t-test, and what alternatives exist if they are violated?
  3. What is the difference between statistical significance and practical importance?
  4. How would your confidence interval change at 99% instead of 95%?
  5. How would you explain a p-value to someone with no statistical background?

Review and checkpoint

At this point you can:

Note

📤 What to turn in before next class

Upload both of these to the course management system:

  1. Your code — the scripts/ folder (or just 06_T_tests.R)
  2. This worksheet, with your written answers

Take-Home Extension — Mouse Body Mass and the Island Rule

Due Monday, September 28 — before the Power & Error class.

This uses the same machinery you just ran, on a new dataset and a new question. This time you decide which test to run and defend that choice; nobody walks you through it.

Background

The Island Rule predicts that when mainland species colonize islands, small-bodied species — like rodents — tend to evolve larger body size, released from mainland predation and competition. Below are body mass measurements for deer mice trapped on Sidney Island (island) and near Vancouver, BC (mainland).

▶ Run this:

# ── Part 5.1 · Take-home extension ──────────────────────────────────────
mice_df <- read_csv("data/mice_weights.csv")

mice_df %>% group_by(location) %>% reframe(summary_stats(mass_g))

Task A · A one-sample question

Question: Do island mice (Sidney Island) differ from a “typical” continental deer mouse body mass of 19 g?

💡 This is Section 2 again, with a different mu and a different dataset.

✏️ Your turn:

Question:     ______________________________________________________

Prediction:   ______________________________________________________

              ______________________________________________________

H0 =          ______________________________________________________

Ha =          ______________________________________________________

Test I will use, and why: __________________________________________

              ______________________________________________________
# ── Part 5.2 · Task A ───────────────────────────────────────────────────
# Write your code in the R script:
Result:  t(______) = ________, p = ________

Interpretation:

____________________________________________________________________

____________________________________________________________________

Task B · A two-sample question

Question: Does mouse body mass differ between the island (Sidney Island) and mainland (Vancouver) populations?

💡 In Section 3 you used Levene’s test to choose between the standard and Welch’s two-sample t-test. Do that check again here, on your own, before you pick one.

✏️ Your turn:

Question:     ______________________________________________________

Prediction:   ______________________________________________________

              ______________________________________________________

H0 =          ______________________________________________________

Ha =          ______________________________________________________

Levene's p =  ____________

Test I will use, and why: __________________________________________

              ______________________________________________________
# ── Part 5.3 · Task B ───────────────────────────────────────────────────
# Write your code in the R script:
Result:  t(______) = ________, p = ________

Interpretation:

____________________________________________________________________

____________________________________________________________________

✏️ Your turn: Could you have run a paired t-test on these mice? Why or why not?

____________________________________________________________________

____________________________________________________________________

Final Figure

Produce one figure summarizing island versus mainland body mass. This is the one place in this worksheet where you should dress it up — proper axis labels, no default grey background — and export it with ggsave().

# ── Part 5.4 · Final figure ─────────────────────────────────────────────
# Write your code in the R script:
Note

📤 What to turn in — due Sept 28

For both Task A and Task B:



Homework

Homework 3 — T-Tests

One- and two-sample t-tests on mice from the San Juan Islands and the mainland, testing the island rule: do island rodents evolve larger bodies? You test each population against a stated population mean of 19 g, and then test island against mainland.

Data: mice_weights.csv — a course dataset; the biology behind it is Foster’s rule (1964).