Lecture 10 — T-Tests II: Running & Reporting the Test

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

tidyverse
descriptive-stats

Running Welch’s two-sample t-test, decoding every line of the output, making a formal decision, and writing a proper scientific results sentence.

Author

Bill Perry

Published

September 10, 2026

Where we left off (Lecture 09 — T-Tests I)

  • H₀: \(\mu_{shady} = \mu_{sunny}\) · Hₐ: \(\mu_{shady} \neq \mu_{sunny}\) (two-tailed, α = 0.05)
  • Normality checked — histogram + Shapiro-Wilk, roughly bell-shaped for ~25 per group
  • Variance checked — Levene’s test run (we use Welch’s either way)
  • Decision: proceed with Welch’s two-sample t-test
Note

✅ Key idea from Lecture 09

Every box is checked. Today we actually run the test, decode what R hands back, and turn it into a sentence a reader can trust.

Goals for Today

  • Run Welch’s two-sample t-test in R
  • Read every line of the t.test() output
  • Decide: reject or fail to reject H₀
  • Visualize the result with the test statistics embedded
  • Report results in proper scientific writing format
Tip

🖐 Try it yourself

By the end you will have a complete, reportable t-test result and a publication-style figure to go with it — whatever the test says.

References:

  • 📖 Whitlock & Schluter, Ch. 4 — Estimating with Uncertainty
  • 📖 R4DS §3

Both are in the course readings/ folder.

Today’s naming:

  • test objects → _model
  • plots → _plot

How to Use These Slides — Predict · Type · Run

This lecture runs in two short chunks. After each chunk you switch to the activity and type the code yourself into your R script.

For every code block, do three things:

  1. Predict — before it runs, say what you think the output will be
  2. Type it out by hand — do not copy-paste
  3. Run it and compare to your prediction
Note

✅ Why bother?

Guessing “above or below 0.05” before you see t.test() output forces you to reason about the test instead of just reading a number off the screen — that habit is what makes a p-value meaningful instead of magic.

Load Libraries and Data

# Load all packages at the top ----------------------------
library(readxl) # reading Excel files
library(tidyverse) # data manipulation + ggplot2
library(janitor) # clean_names()
library(car) # Levene's test for variance equality
# Load the leaf data from the data folder -----------------
leaf_df <- read_excel("data/2026_09_03_data_sci_leaf_area.xlsx") %>%
  clean_names()

Quick Recap of Our Assumption Checks

# Rerun the assumption checks quickly for reference -----
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
# A tibble: 2 × 5
  shade     n mean_mass sd_mass se_mass
  <chr> <int>     <dbl>   <dbl>   <dbl>
1 shady    28     0.523   0.148   0.028
2 sunny    25     0.505   0.21    0.042
leveneTest(mass_g ~ shade, data = leaf_df)
Levene's Test for Homogeneity of Variance (center = median)
      Df F value  Pr(>F)  
group  1  2.9667 0.09105 .
      51                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
  • Both groups: roughly normal (Lecture 09)
  • Levene’s: recorded, but does not change our test choice
  • We proceed with Welch’s (var.equal = FALSE)
Tip

If your Activity 09 numbers don’t match exactly, that’s fine — small rounding differences don’t change the conclusion.

🧩 Chunk 1 of 2 · Run & Interpret

We will cover: running Welch’s t.test(), reading every line of the output, and making a formal reject / fail-to-reject decision.

🛑 After this chunk: Activity Parts 1–3 (run, decode, decide).

Step 4 — Run the Welch’s t-Test

Note

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

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

leaf_ttest_model

    Welch Two Sample t-test

data:  mass_g by shade
t = 0.35579, df = 42.527, p-value = 0.7238
alternative hypothesis: true difference in means between group shady and group sunny is not equal to 0
95 percent confidence interval:
 -0.08392774  0.11987117
sample estimates:
mean in group shady mean in group sunny 
          0.5230357           0.5050640 

Key arguments:

argument what it does
mass_g ~ shade compare mass_g between levels of shade
var.equal = FALSE use Welch’s correction
alternative = "two.sided" two-tailed test

The result is a model object. We decode each line of the output on the next slide.

Reading the t.test() Output

# Extract individual values from the model object ------
cat("t-statistic  :", round(leaf_ttest_model$statistic, 3), "\n")
t-statistic  : 0.356 
cat("df (Welch)   :", round(leaf_ttest_model$parameter, 2), "\n")
df (Welch)   : 42.53 
cat("p-value      :", signif(leaf_ttest_model$p.value, 3), "\n")
p-value      : 0.724 
cat(
  "95% CI       :",
  round(leaf_ttest_model$conf.int[1], 3),
  "to",
  round(leaf_ttest_model$conf.int[2], 3),
  "g\n"
)
95% CI       : -0.084 to 0.12 g
cat("Mean shady   :", round(leaf_ttest_model$estimate[1], 3), "g\n")
Mean shady   : 0.523 g
cat("Mean sunny   :", round(leaf_ttest_model$estimate[2], 3), "g\n")
Mean sunny   : 0.505 g

Line by line:

output meaning
t our calculated t-statistic
df Welch-Satterthwaite df (non-integer is normal!)
p-value probability of this result if H₀ is true
95% CI plausible range for the true difference in means
mean of shady/sunny the two group means

If the 95% CI includes zero, “no difference” is one of the plausible values — consistent with a p above 0.05.

Step 5 — Make a Decision

# State the decision formally --------------------------
# if/else: R checks the condition and runs one of two blocks
# We will learn these formally in the functions lecture
p_val <- leaf_ttest_model$p.value
alpha <- 0.05

if (p_val < alpha) {
  cat("p =", signif(p_val, 3), "< α =", alpha, "\n")
  cat("Decision: REJECT H₀\n")
  cat("Conclusion: mean leaf mass differs between sides.\n")
} else {
  cat("p =", signif(p_val, 3), ">= α =", alpha, "\n")
  cat("Decision: FAIL TO REJECT H₀\n")
  cat("Conclusion: no evidence that mean leaf mass differs between sides.\n")
}
p = 0.724 >= α = 0.05 
Decision: FAIL TO REJECT H₀
Conclusion: no evidence that mean leaf mass differs between sides.
Important

What the p-value means:

If H₀ were true (no real difference), we would get a t-statistic at least this large p × 100% of the time just by chance.

We set α = 0.05 before the test. If p < α, we reject H₀; otherwise we fail to reject it.

What “fail to reject” does NOT mean:

  • It is not proof that H₀ is true
  • It means this sample did not provide enough evidence of a difference
  • Report the effect size (difference in means) and CI alongside p

→ ACTIVITY 10 Parts 1–3 now

🛑 Do Activity Parts 1–3 now

Run the test, decode t / df / p / CI, and state your decision in formal language. Predict the p-value first.

🧩 Chunk 2 of 2 · Visualize & Report

We will cover: embedding the test result in a boxplot and writing a scientific results sentence.

🛑 After this chunk: Activity Parts 4–5 (plot and the results sentence).

Visualize the Result

# Final boxplot with all points -----------------------
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

Boxplot of leaf mass in grams by shade with jittered individual points overlaid, subtitled with the Welch's t-test statistic and p-value.

Including the test result in the figure:

  • subtitle pulls t and p directly from the model object
  • R fills in the actual numbers automatically
  • If you re-run with new data, the subtitle updates
Tip

Embedding test statistics in the plot subtitle saves you from copying numbers by hand — and avoids typos in your report.

How to Report Results in Science

# Build the results sentence from the model object -----
t_val  <- round(leaf_ttest_model$statistic, 2)
df_val <- round(leaf_ttest_model$parameter, 1)
p_val2 <- signif(leaf_ttest_model$p.value, 2)

heavier <- if (leaf_ttest_model$estimate[1] > leaf_ttest_model$estimate[2]) "shady" else "sunny"
lighter <- if (heavier == "shady") "sunny" else "shady"

cat("--- Results sentence ---\n")
--- Results sentence ---
if (leaf_ttest_model$p.value < 0.05) {
  cat(heavier, "-side leaves were significantly heavier than ",
      lighter, "-side leaves\n", sep = "")
} else {
  cat("Mean leaf mass did not differ significantly between sides\n")
}
Mean leaf mass did not differ significantly between sides
cat("(Welch's t-test: t(", df_val, ") = ", t_val, ", p = ", p_val2, ").\n", sep = "")
(Welch's t-test: t(42.5) = 0.36, p = 0.72).
cat(
  "Mean ± SE: shady =",
  round(leaf_ttest_model$estimate[1], 3),
  "±",
  round(recap_df$se_mass[recap_df$shade == "shady"], 3),
  "g,  sunny =",
  round(leaf_ttest_model$estimate[2], 3),
  "±",
  round(recap_df$se_mass[recap_df$shade == "sunny"], 3),
  "g.\n"
)
Mean ± SE: shady = 0.523 ± 0.028 g,  sunny = 0.505 ± 0.042 g.

Standard format:

“[Finding] (Welch’s t-test: t(df) = X.XX, p = X.XXX; mean ± SE: group1 = X.X ± X.X units, group2 = X.X ± X.X units).”

Always include:

  • Test type (Welch’s t-test)
  • t-statistic and df
  • Exact p-value (or < 0.001)
  • Group means ± SE with units

A non-significant result is still a result — report it plainly (“did not differ significantly”), never as “proved there is no difference.”

Never report “p = 0.000” — write “p < 0.001” instead.

Save Your Plot

# Save the plot to the figures folder ------------------
ggsave(
  "figures/leaf_mass_boxplot_ttest.png",
  plot = leaf_box_10_plot,
  width = 5,
  height = 5,
  units = "in",
  dpi = 300
)

Your plot is now in your figures/ folder with the test statistics embedded in the subtitle — ready for a lab report or paper.

Tip

dpi = 300 is publication quality. Use it for any figure that will appear in a paper, poster, or formal report.

→ ACTIVITY 10 Parts 4–5 now

🛑 Go to Activity 10 — Parts 4–5

Build the figure with the statistics embedded in the subtitle, then write your results sentence. Predict what the subtitle will say before you run it.

What We Learned Today

Concepts:

  • Steps 4–5 of the five-step framework — run, interpret, report
  • Reading output — t, df, p, CI, group means
  • A results sentence needs: test name, t, df, p, means ± SE, units
  • A non-significant result is reported plainly — not as proof of “no difference”

R skills:

  • t.test(y ~ group, data, var.equal = FALSE, alternative = "two.sided")
  • model$statistic, model$p.value, model$conf.int — extract results
  • paste0(...) in labs(subtitle = ) — embed test statistics in figures

References:

  • 📖 Whitlock & Schluter, Ch. 4 — Estimating with Uncertainty
  • 📖 R4DS §3

Up next — Lecture 11:

  • Linear regression — predicting leaf area from paper tracing mass
  • Building a calibration curve with lm()
  • R², residuals, and assumption checking