Activity 10 — T-Tests II: Running, Reporting & Extension

Running Welch’s t-test, reading the output, and writing it up

r-basics
project-setup

Hands-on companion to T-Tests II: run a Welch’s two-sample t-test in R, decode the output, make a decision, and write up the results.

Author

Bill Perry

Published

September 10, 2026

In-class Activity 10: Running & Reporting the Test

Recap from Activity 09 (T-Tests I)

  • Stated H₀ and Hₐ formally (two-tailed, α = 0.05)
  • Checked normality with a histogram and Shapiro-Wilk
  • Checked variance with Levene’s test
  • Decided: proceed with Welch’s t-test (var.equal = FALSE)

Today’s Objectives

  1. Run a Welch’s two-sample t-test (var.equal = FALSE, two-tailed)
  2. Read and decode every line of the t.test() output
  3. Make a formal statistical decision and state a conclusion
  4. Write a results sentence in proper scientific format

How this activity works

You keep building the script from Activity 09 — add today’s code to the bottom of scripts/09_t_tests_1.R, or start scripts/10_t_tests_2.R and reload the libraries and data at its top. Either way, you turn the script in.

  • Every line you run goes in that script — not the Console, not this page. Type it, don’t paste it.
  • Start every code chunk with a short # comment saying what it does.
  • Code marked ▶ Run this is typed in exactly as shown. Code marked ✏️ Your turn is a change you make and run.
  • The Extension at the end is done out of class (~30–40 min) and turned in with the script.

🔮 Predict before you run

Before you run t.test(), write down a guess for the p-value — above or below 0.05? Then run it and see how close you were.


🧩 Chunk 1 — Run & interpret (after lecture Chunk 1)

Parts 1–3: run the test, decode the output, and decide.

Part 1 · Load libraries and data, and recap assumptions

Tip📂 Get the data

Same leaf file as Activity 09 — it should already be in your project’s data/ folder. If not: 2026_09_03_data_sci_leaf_area.xlsx → put it in data/.

▶ Run this at the top of your script:

# ---- Libraries -----------------------------------------
library(readxl)      # read Excel files
library(tidyverse)   # dplyr + ggplot2
library(janitor)     # clean_names()
library(car)         # Levene's test for variance
# ---- Load data and re-run last lecture's checks --------
leaf_df <- read_excel("data/2026_09_03_data_sci_leaf_area.xlsx") %>%
  clean_names()

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: From Activity 09, what was your decision about normality and variance? Were you clear to proceed with Welch’s?

Normality decision:
Variance (Levene's) decision:
Proceed with Welch's?  Y / N

Part 2 · Run the Welch’s t-test

🔮 Predict first: Write down a guess for the p-value now — above or below 0.05? Then run t.test() and see how close you were.

▶ Run this:

# Welch's two-sample t-test --------------------------
leaf_ttest_model <- t.test(
  mass_g ~ shade,          # response ~ grouping variable
  data        = leaf_df,
  var.equal   = FALSE,      # Welch's — no pooled variance
  alternative = "two.sided" # two-tailed test
)

leaf_ttest_model

✏️ Your turn: Copy the full output below:

Paste or write the t.test() output here:

Part 3 · Decode the output and decide

▶ Run this to extract individual values:

# Pull specific values from the model object ---------
cat("t-statistic:", round(leaf_ttest_model$statistic,  3), "\n")
cat("df (Welch): ", round(leaf_ttest_model$parameter,  2), "\n")
cat("p-value:    ", signif(leaf_ttest_model$p.value,   3), "\n")
cat("95% CI:     ", round(leaf_ttest_model$conf.int[1], 3),
    "to", round(leaf_ttest_model$conf.int[2], 3), "g\n")
cat("Mean shady: ", round(leaf_ttest_model$estimate[1], 3), "g\n")
cat("Mean sunny: ", round(leaf_ttest_model$estimate[2], 3), "g\n")

✏️ Your turn: Match each piece of output to its meaning:

t-statistic = _____

  → This is large/small (circle one) because the difference in means
    is large/small relative to the variability (circle one).

df = _____

  → This is a non-integer. Why? (hint: Welch-Satterthwaite equation)

p-value = _____

  → In plain language, this means: if H₀ were true, the probability
    of seeing a t-statistic this extreme just by chance is _____.

95% CI = _____ to _____ g

  → This CI is for the ________________ (the true difference in means).
    It does / does not include zero (circle one).
    What does it mean when the CI includes zero?

✏️ Your turn: State your decision and conclusion. Use the formal language of hypothesis testing.

Our p-value:
Our α:

Decision (circle one):   REJECT H₀   /   FAIL TO REJECT H₀

In one sentence, what does this mean biologically?

💡 Key idea: We never say “H₀ is true” or “H₀ is false.” We say “we reject H₀” (evidence was strong enough) or “we fail to reject H₀” (evidence was not strong enough). Failing to reject H₀ is not proof there is no difference — only that this sample did not show one.


🧩 Chunk 2 — Visualize & report (after lecture Chunk 2)

Parts 4–5: build the figure with the statistics embedded, then write the results sentence.

Part 4 · Visualize the result

Boxplot with test statistics in the subtitle

▶ Run this:

# Boxplot with t and p embedded in subtitle ----------
leaf_box_10_plot <- leaf_df %>%
  ggplot(aes(x = shade, y = mass_g, fill = shade)) +
  geom_boxplot(alpha = 0.5, outlier.shape = NA) +
  geom_point(
    position = position_jitter(width = 0.15, seed = 42),
    alpha = 0.6, size = 2.5
  ) +
  labs(
    title    = "Leaf Mass by Shade",
    subtitle = paste0("Welch's t-test: t = ",
                      round(leaf_ttest_model$statistic, 2),
                      ", p = ",
                      signif(leaf_ttest_model$p.value, 2)),
    x = "Shade",
    y = "Leaf Mass (g)"
  ) +
  theme_minimal() +
  theme(legend.position = "none")

leaf_box_10_plot

✏️ Your turn: The subtitle line uses paste0() to build the label from the model object. Why is this better than typing the numbers into subtitle = "..." yourself?

Your answer:

Save the plot

▶ Run this:

ggsave("figures/leaf_mass_boxplot_ttest.png",
       plot   = leaf_box_10_plot,
       width  = 5,
       height = 5,
       units  = "in",
       dpi    = 300)

Part 5 · Write a scientific results sentence

✏️ Your turn: Write a complete results sentence as you would in a lab report or paper. Report what the test actually showed — whether or not it was significant. Include:

  • The test name, t-statistic, df, and p-value in parentheses
  • Mean ± SE for both groups with units
  • If significant: which group was larger, with the word “significantly”
  • If not significant: say the means “did not differ significantly”

Use whichever template fits your result:

"[Group] leaves were significantly [heavier / lighter] than [group] leaves
(Welch's two-sample t-test: t(___) = ___, p = ___).
Mean ± SE: [side 1] = ___ ± ___ g, [side 2] = ___ ± ___ g."

  — or —

"Mean leaf mass did not differ significantly between shady and sunny sides
(Welch's two-sample t-test: t(___) = ___, p = ___).
Mean ± SE: shady = ___ ± ___ g, sunny = ___ ± ___ g."

Write your sentence here:

Your results sentence:

💡 Key idea: Never write p = 0.000. Write p < 0.001 instead. A p-value is never exactly zero — it is just too small to display.


Part 6 · 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:

Extension — out of class (~30–40 min)

Add this to the bottom of your script and turn it in. Put your written answers in # comments under the code they go with. In class we ran a two-tailed test on leaf mass. The extension changes two things: a new variable you choose and a one-tailed (directional) hypothesis — which behaves differently and is easy to misuse.

E1 · A directional test on a new variable (3 pts)

Pick one variable you did not test in class: petiole_mm or thickness_mm. Write down which you picked and, before running anything, a directional hypothesis — e.g. “shady leaves are larger”, i.e. Hₐ: μ_shady > μ_sunny.

# Replace ___ with "petiole_mm" or "thickness_mm".
# alternative is "greater"/"less" for the FIRST group alphabetically
# ("shady" comes before "sunny"), so "shady larger" = "greater".
one_tail_model <- t.test(
  ___ ~ shade,
  data        = leaf_df,
  var.equal   = FALSE,
  alternative = "greater"      # <- directional; change if your Ha points the other way
)
one_tail_model

# For comparison, the two-tailed version of the SAME test
t.test(___ ~ shade, data = leaf_df, var.equal = FALSE)$p.value

Record (in comments):

Variable chosen:
Directional Ha (in symbols):
One-tailed p-value:
Two-tailed p-value:
Ratio (two-tailed / one-tailed) ≈
Decision at alpha = 0.05:

E2 · Predict, then check (3 pts)

Before running E1, in comments:

  1. Predict the relationship between the one-tailed and two-tailed p-values for the same data. Which is smaller, and roughly by how much?
  2. Predict whether your chosen variable will differ significantly between sides, and why.

Then run E1 and write your actual one- and two-tailed p-values and whether your predictions held.

E3 · Explain it, with YOUR numbers (3 pts)

Using your real output:

  1. Explain why the one-tailed p-value is (about) half the two-tailed one — in terms of where the rejection region sits on the t distribution.
  2. A one-tailed test is only legitimate if the direction was chosen before seeing the data. Explain, in plain language, what goes wrong if a researcher runs a two-tailed test, sees p = 0.08, and then switches to the one-tailed test that “works”.
  3. If your one-tailed result is significant but the two-tailed result is not, which would you report in a paper, and why?

Optional, no points: rerun E1 with conf.level = 0.99 and note how the CI changes — and why a one-tailed t.test() reports a one-sided CI (-Inf or Inf at one end).


What your figures/ folder should contain

tree_project/
├── data/
│   └── 2026_09_03_data_sci_leaf_area.xlsx
├── figures/
│   ├── leaf_mass_mean_se.png              <- from Activity 03 (GGPlot I)
│   └── leaf_mass_boxplot_ttest.png        <- from this activity
└── scripts/
    └── 10_t_tests_2.R                     <- your script (turn this in)

Getting unstuck

  1. Read the error message out loud. R usually names the line and the problem.
  2. t.test() formula: the grouping variable goes on the right of ~ and must have exactly two levels. Check with unique(leaf_df$shade).
  3. Cheat sheetshttps://posit.co/resources/cheatsheets/
  4. Bring the exact error (copy-paste it) to class, Canvas, or office hours.

💡 Key idea: Nothing in the five-step sequence — hypotheses → normality → variance → test → report — was specific to leaf mass or to comparing exactly two sides. Swap in three species instead of two, and it’s the same sequence you’ll run for ANOVA.


End of the T-Tests II activity. Next: regression.