pbc <- survival::pbc %>%
as_tibble() %>%
janitor::clean_names() %>%
mutate(
trt = factor(trt, levels = c(1, 2), labels = c("D-penicillamine", "Placebo"))
)4 Hypothesis Testing: Concepts and Framework
Total core time: about 115 minutes (~2 hours).
| Section | Time | Type |
|---|---|---|
| When Do You Use This? + Learning Objectives | 5 min | Reading |
| Background: The Logic of Hypothesis Testing | 25 min | Reading |
| Example 1: Clinical Data (PBC) | 20 min | Worked example |
| Example 2: Multiple Testing | 15 min | Worked example |
| What Can Go Wrong | 10 min | Reading |
| Exercises 1 & 2 | 30 min | Practice |
| Comprehension Check | 10 min | Self-test |
In a taught course, this session fits one teaching block. For self-paced study, split it into two sittings, e.g. Background and Example 1 first, Example 2 onward later. Exercise 3 is open-ended and works best as a follow-up using a paper from your own field.
Every code chunk below is written so you can copy it into your R console (or an R script) and run it yourself, in the order shown. Where you see a “Run It Yourself” box, stop and run the code above it before reading the explanation that follows.
4.1 When Do You Use This?
You have data from two groups of patients and you want to know whether they genuinely differ, or whether the difference you observe could simply be due to the luck of which patients ended up in each group. Or you have a new treatment and you want to know whether its effect is real or just noise. Hypothesis testing gives you a principled, reproducible framework for making these decisions from data, while keeping the rate of false positives under control.
This session builds directly on the previous one. In Sampling, Estimation, and Confidence Intervals you learned how to estimate a population quantity and quantify your uncertainty. Hypothesis testing is the next step: instead of just estimating, you are now making a yes/no decision: is the effect real?
Open by asking the room for a time they made an informal “is this real or just noise?” judgement: a slightly higher reading on a second measurement, a colleague’s result that didn’t replicate, a small change between two runs of an experiment. Use one of those examples to motivate the criminal-trial analogy below before any formula appears.
4.2 Learning Objectives
After completing this session you will be able to:
- State null and alternative hypotheses for a comparison
- Explain what a p-value means, and what it does not mean
- Describe Type I and Type II errors and how they relate to \(\alpha\) and \(\beta\)
- Explain statistical power and the factors that determine it
- Apply multiple testing corrections and explain when each is appropriate
- Relate hypothesis tests to confidence intervals
4.3 Background: The Logic of Hypothesis Testing
Estimated time: ~25 minutes (reading)
The criminal trial analogy below is the single most useful framing in this session: almost every later confusion about p-values traces back to forgetting that a hypothesis test starts by assuming \(H_0\) is true. Before reading on, ask the room: “in a criminal trial, does ‘not guilty’ mean the jury believes the defendant is innocent?” The answer (“no, it means the evidence wasn’t strong enough to convict”) is exactly the relationship between “p > 0.05” and “no effect” that comes up later in this session.
4.3.1 The criminal trial analogy
Before any formulas, here is the underlying logic in a context you already understand.
In a criminal trial, the defendant is presumed innocent until proven guilty. The court does not ask “is this person guilty?”; it asks “is the evidence against an innocent person this damning, or more so?” If the answer is yes (the evidence would be very unlikely if the person were truly innocent), you reject the presumption of innocence and convict.
Hypothesis testing follows the exact same logic:
- The null hypothesis (\(H_0\)) is the default position: usually “there is no effect” or “the groups are the same.” This is the presumption of innocence.
- You do not ask “is the effect real?” You ask: “How likely is it that I would observe data this extreme or more extreme if the null hypothesis were actually true?”
- If that probability is very small (below your threshold \(\alpha\)), you reject the null hypothesis.
That probability is the p-value. And just as a conviction does not prove guilt beyond all doubt (only beyond a reasonable doubt), a small p-value does not prove your hypothesis is true. It only says the data are sufficiently inconsistent with the null.
4.3.2 The five steps
Every hypothesis test follows the same sequence:
- State hypotheses: define \(H_0\) (null) and \(H_1\) (alternative) before looking at the data.
- Explore the data: plot first. Always. You may find obvious violations of assumptions, unexpected outliers, or a result that clearly does not need a test.
- Check assumptions and choose the test: select the test appropriate for your data type, design, and sample size.
- Compute the test statistic and p-value: run the test in R, read the output line by line.
- Interpret: report the test statistic, p-value, effect size, and confidence interval. State your conclusion in plain language.
4.3.3 What a p-value is, and is not
What a p-value IS: The probability of observing a test statistic as extreme as (or more extreme than) the one you got, assuming the null hypothesis is true.
What a p-value is NOT:
- The probability the null hypothesis is true
- The probability your result is due to chance
- A measure of the size or importance of the effect
- A pass/fail grade for your study
The most common misstatement in science is: “p = 0.03 means there is a 3% chance the result is a fluke.” This is wrong. The p-value is computed under the assumption that the null is true. It cannot tell you the probability that the null is true: that requires Bayesian reasoning.
The correct statement: “If the null hypothesis were true, the probability of observing a test statistic this extreme or more extreme is 3%.”
A large p-value (say, p = 0.6) does not mean there is no effect. It means your data are consistent with the null hypothesis. There may be a real effect that your study was too small to detect. This is why “p > 0.05” and “no effect” are not the same thing.
4.3.4 Type I and Type II errors
Board exercise: draw the 2x2 table below empty, then describe a diagnostic test scenario (e.g. a screening test for a disease) and ask the room to fill in each cell with a concrete consequence (“patient told they have the disease but does not”, etc.). Once the table is filled, introduce the smoke-alarm analogy and ask students to map it onto their own diagnostic-test table. Connecting the abstract 2x2 table to two concrete scenarios in succession is what makes \(\alpha\), power, and \(\beta\) stick.
Every decision rule makes errors. Here are the two kinds:
| \(H_0\) is true | \(H_0\) is false | |
|---|---|---|
| Reject \(H_0\) | Type I error (false positive): probability \(= \alpha\) | Correct (true positive): probability \(= \text{power}\) |
| Fail to reject \(H_0\) | Correct (true negative) | Type II error (false negative): probability \(= \beta\) |
Power \(= 1 - \beta\) is the probability of detecting a real effect of a given size. A study with 80% power has a 20% chance of missing a real effect.
Think of a smoke alarm. A Type I error is the alarm going off when there is no fire (someone burnt toast). A Type II error is the alarm staying silent when there actually is a fire. Lowering the sensitivity of the alarm (\(\alpha\)) reduces false positives but increases missed fires.
Power increases when:
- Sample size increases: the most reliable lever
- Effect size is larger: big effects are easier to detect
- Variability is smaller: cleaner measurements help
- \(\alpha\) is larger, but this increases the Type I error rate
4.3.5 The connection to confidence intervals
A 95% confidence interval and a two-sided hypothesis test at \(\alpha = 0.05\) are two ways of saying the same thing:
- If the 95% CI for a difference excludes zero, the test is significant at p < 0.05.
- If the 95% CI includes zero, the test is not significant.
The CI is usually more informative than the p-value because it tells you both whether the effect is significant and how large it plausibly is. Report both.
4.4 Example 1: Clinical Data (PBC)
Estimated time: ~20 minutes (worked example)
Before running anything, take a quick show-of-hands poll: “do you expect this baseline test to come out significant (p < 0.05) or not?” Most students will correctly guess “not significant” once reminded that the groups were randomised, but the poll makes the prediction explicit and gives the eventual result more weight. The Sampling and Estimation session computed a CI for this same albumin variable; if the group covered that session, this is a natural callback.
The question: does serum albumin differ between patients who received D-penicillamine versus those who received placebo?
This is a clinical trial, so the groups were randomised. Before we test the primary outcome (which comes after treatment), it is standard practice to check baseline comparability: were the groups similar on key characteristics before the intervention began? Albumin at baseline should be similar between groups if the randomisation worked.
Think about this for a moment before running the test: what result do you expect to find when testing baseline albumin in a well-randomised trial?
4.4.1 Step 1: State the hypotheses
\[H_0: \mu_{\text{D-pen}} = \mu_{\text{placebo}}\] \[H_1: \mu_{\text{D-pen}} \neq \mu_{\text{placebo}}\]
Two-sided, because we have no reason to expect a specific direction. The groups were randomly assigned.
4.4.2 Step 2: Explore the data
Always plot before testing. Look at the distribution and ask: are there obvious outliers? Does the spread look similar between groups?
pbc %>%
filter(!is.na(trt), !is.na(albumin)) %>%
group_by(trt) %>%
summarise(
n = n(),
mean = mean(albumin),
sd = sd(albumin),
median = median(albumin)
)# A tibble: 2 × 5
trt n mean sd median
<fct> <int> <dbl> <dbl> <dbl>
1 D-penicillamine 158 3.52 0.443 3.56
2 Placebo 154 3.52 0.396 3.54
pbc %>%
filter(!is.na(trt), !is.na(albumin)) %>%
ggplot(aes(x = trt, y = albumin, fill = trt)) +
geom_boxplot(alpha = 0.7, outlier.shape = NA) +
geom_jitter(width = 0.15, alpha = 0.3, size = 1.5) +
scale_fill_manual(values = c("steelblue", "tomato")) +
labs(
title = "Serum albumin by treatment group (baseline)",
x = NULL,
y = "Albumin (g/dL)"
) +
theme_bw() +
theme(legend.position = "none")
Run the chunk above. Both groups should show mean ≈ 3.52, with similar SDs (0.44 vs 0.40) and medians (3.56 vs 3.54). The boxplot should show two distributions that look almost identical, heavily overlapping. Based on this, what would you predict for the t-test p-value in the next chunk: small (< 0.05) or large (> 0.05)?
The two groups look nearly identical. The means differ by less than 0.05 g/dL. Before even running the test, the plot strongly suggests a null result, and that is exactly what we want to see for a baseline variable.
4.4.3 Step 3: Check assumptions and choose the test
The two-sample t-test assumes:
- Observations are independent (patients are not paired or related)
- The sampling distribution of the mean is approximately normal (CLT holds with ~150 patients per group)
- Welch’s t-test (the R default) relaxes the equal-variance assumption, so we do not need to test for that
4.4.4 Step 4: Perform the test
t.test(albumin ~ trt,
data = pbc %>% filter(!is.na(trt)),
var.equal = FALSE) # Welch's t-test (default; always use this)
Welch Two Sample t-test
data: albumin by trt
t = -0.15909, df = 307.66, p-value = 0.8737
alternative hypothesis: true difference in means between group D-penicillamine and group Placebo is not equal to 0
95 percent confidence interval:
-0.1011356 0.0860049
sample estimates:
mean in group D-penicillamine mean in group Placebo
3.516266 3.523831
Run the chunk above. You should get t ≈ -0.16, df ≈ 307.7, p ≈ 0.87, and a 95% CI for the difference of about (-0.10, 0.09). The group means are about 3.516 (D-pen) and 3.524 (Placebo), both rounding to 3.52. Does this match your prediction from the plot?
| Output | What it means |
|---|---|
| t = −0.16 | Test statistic: the difference between means divided by the standard error. A value near zero means the difference is about the size we would expect from random sampling alone. |
| df = 307.7 | Welch’s degrees of freedom. Not simply n₁ + n₂ − 2 because it accounts for the possibility of unequal variances. |
| p-value = 0.87 | If there were truly no difference in albumin, we would see a gap this large or larger 87% of the time just from sampling variation. Strong evidence that the groups are similar. |
| 95% CI: −0.10 to 0.09 | The difference in means (D-pen minus placebo) is plausibly between −0.10 and +0.09 g/dL. Because the interval contains zero, the difference is not statistically significant. |
| Means: 3.52 vs 3.52 | The actual values (3.516 vs 3.524 to three decimals). A ~0.01 g/dL gap is both statistically and clinically negligible. |
4.4.5 Step 5: Interpret
The result is exactly what we hoped for: no statistically significant difference in baseline albumin between groups (p = 0.87, 95% CI for difference: −0.10 to 0.09 g/dL). This confirms that randomisation produced two comparable groups, which is reassuring for the validity of the trial.
“Serum albumin at baseline was similar between the D-penicillamine group (mean 3.52 g/dL) and placebo group (mean 3.52 g/dL; Welch’s t-test: t(307.7) = −0.16, p = 0.87, mean difference −0.01 g/dL, 95% CI: −0.10 to 0.09).”
Note: always report the means for both groups, the test statistic with degrees of freedom, the exact p-value, and the 95% CI for the difference. “p = 0.87” alone tells the reader almost nothing.
4.5 Example 2: Multiple Testing
Estimated time: ~15 minutes (worked example)
Live activity: ask everyone in the room to silently pick a random number between 0 and 1 (or generate one on their phone/laptop). Then ask everyone whose number is below 0.05 to raise their hand. With a class of 20, you would expect about 1 hand. This makes the “1 false positive per 20 tests” arithmetic concrete before the simulation below runs the same idea computationally.
When you run many tests at once, the p < 0.05 threshold becomes dangerous. If you test 20 genes and none are truly differentially expressed, you still expect \(20 \times 0.05 = 1\) false positive by chance alone. This is not a statistical quirk; it is a direct consequence of the definition of \(\alpha\).
Imagine you are testing 20 different drugs against placebo. All 20 are inert sugar pills. If you declare victory whenever p < 0.05, you would on average find one “effective” drug per trial just by luck. This is the multiple testing problem.
set.seed(2024)
# Simulate 20 p-values where the null is true for every test
p_values <- runif(20, min = 0, max = 1)
# How many look "significant" by chance?
sum(p_values < 0.05)[1] 0
# Bonferroni: divide alpha by the number of tests
p_bonf <- p.adjust(p_values, method = "bonferroni")
sum(p_bonf < 0.05)[1] 0
# Benjamini-Hochberg FDR: controls proportion of false discoveries
p_bh <- p.adjust(p_values, method = "BH")
sum(p_bh < 0.05)[1] 0
Run the chunk above with set.seed(2024). All three lines should print 0: none of the 20 raw p-values are below 0.05, and so none survive Bonferroni or BH correction either. Every one of these 20 tests is testing a true null, so this is a “good” outcome, but it is partly luck: on average you’d expect about \(20 \times 0.05 = 1\) false positive among the raw p-values. Try removing set.seed(2024) and re-running a few times. How often do you see 1 or more raw p-values below 0.05?
| Method | Controls | Use when |
|---|---|---|
| Bonferroni | Family-wise error rate (probability of any false positive) | False positives are very costly: clinical decisions, regulatory submissions, few pre-specified tests |
| Benjamini–Hochberg (FDR) | False discovery rate (proportion of discoveries that are false) | Screening many hypotheses: genomics, proteomics, GWAS |
| None | Nothing | Single pre-specified primary hypothesis only |
Bonferroni is conservative: it will miss real effects when you run many tests. For genomic studies with thousands of tests, use FDR. For a clinical trial with five pre-specified endpoints, use Bonferroni (or better: specify one primary endpoint in your protocol).
4.6 What Can Go Wrong
Estimated time: ~10 minutes (reading)
The “Common misinterpretations” callout below works well as a quick true/false round: read each statement aloud, ask for a show of hands before revealing why it is wrong. This surfaces misconceptions the group already holds rather than just presenting them.
P-hacking: running tests until something is significant
Every additional test you run on the same data increases the false positive rate. If you test all 50 variables in your dataset and report only the five that came out significant, your Type I error rate is no longer 5%: it is much higher. This is p-hacking, even when done unintentionally. Specify your primary analysis in advance and treat everything else as exploratory.
Confusing statistical and clinical significance
A study of 50,000 patients could detect a 0.1 mmHg reduction in blood pressure as statistically significant (p < 0.001). That would not make it clinically meaningful. Typical antihypertensive treatments aim for reductions of 5–10 mmHg. Statistical significance tells you the effect is distinguishable from zero. Clinical significance tells you whether the effect is large enough to matter. Always report the effect size and CI, not just the p-value.
Using one-sided tests opportunistically
A one-sided test is more powerful but only valid when you specified the direction of the effect before collecting data. Using a one-sided test because the data happened to go the expected way is p-hacking: it halves the p-value without any legitimate justification.
“p = 0.049 and p = 0.051 are meaningfully different results” They are not. Both represent essentially the same strength of evidence. The 0.05 threshold is a convention, not a natural boundary. Report exact p-values and let readers draw their own conclusions.
“p > 0.05 means no effect” It means your sample did not provide enough evidence to reject the null at the chosen threshold. An underpowered study will produce p > 0.05 even when a real effect exists. “No significant effect” and “no effect” are not the same statement.
“Wide confidence intervals make a result non-significant” A CI that excludes zero is significant regardless of width. A narrow CI that includes zero is not significant. Width reflects precision; significance reflects where the interval sits relative to zero.
“A non-significant result is not worth reporting” A well-powered null result is scientifically valuable: it provides an upper bound on how large a real effect could plausibly be. Non- significant results from adequately powered studies should be reported.
4.7 Exercises
In a live session, have students attempt Exercise 1 in pairs (~15 minutes) before revealing the solution, then work Exercise 2 individually. Exercise 3 needs a paper from the student’s own field, so it works best as a take-home or end-of-day task rather than something to complete in the room.
4.7.1 Exercise 1 (Guided): Hypothesis Test for Bilirubin
Estimated time: ~15 minutes
Using survival::pbc, test whether bilirubin (bili) differs between the D-penicillamine and placebo groups at baseline.
- State \(H_0\) and \(H_1\).
- Plot bilirubin by group. Is the distribution symmetric? What transformation should you consider, and why?
- Run a Welch’s t-test on
log(bili). Report the result in one complete sentence including the test statistic, p-value, and 95% CI. - Does the 95% CI include zero? Is this result expected for a baseline variable in a randomised trial?
pbc %>%
filter(!is.na(trt)) %>%
ggplot(aes(x = trt, y = log(bili), fill = trt)) +
geom_boxplot(alpha = 0.7) +
labs(x = NULL, y = "log(Bilirubin)") +
theme_bw() +
theme(legend.position = "none")
t.test(log(bili) ~ trt,
data = pbc %>% filter(!is.na(trt)))
Welch Two Sample t-test
data: log(bili) by trt
t = -0.65231, df = 302.82, p-value = 0.5147
alternative hypothesis: true difference in means between group D-penicillamine and group Placebo is not equal to 0
95 percent confidence interval:
-0.307032 0.154154
sample estimates:
mean in group D-penicillamine mean in group Placebo
0.5379485 0.6143875
The two groups have similar log-bilirubin at baseline. This is expected for a randomised trial: randomisation should produce comparable groups on all baseline characteristics, not just the ones we planned to test. Write-up: “There was no statistically significant difference in bilirubin between treatment groups at baseline (report t, df, p, and CI from your output).”
4.7.2 Exercise 2 (Semi-guided): The Multiple Testing Problem in Practice
Estimated time: ~15 minutes
Simulate 100 hypothesis tests: 90 where the null is true and 10 where there is a real effect.
set.seed(42)
p_null <- replicate(90, t.test(rnorm(30, 0, 1), rnorm(30, 0, 1))$p.value)
p_real <- replicate(10, t.test(rnorm(30, 0.8, 1), rnorm(30, 0, 1))$p.value)
all_p <- c(p_null, p_real)
is_real <- c(rep(FALSE, 90), rep(TRUE, 10))- How many raw p-values are < 0.05? How many of those are true positives (from real effects) and how many are false positives?
- Apply Bonferroni correction. How many survive?
- Apply BH FDR. How many survive?
- Which correction would you use for a clinical trial with 5 pre-specified endpoints? For a GWAS?
set.seed(42)
p_null <- replicate(90, t.test(rnorm(30, 0, 1), rnorm(30, 0, 1))$p.value)
p_real <- replicate(10, t.test(rnorm(30, 0.8, 1), rnorm(30, 0, 1))$p.value)
all_p <- c(p_null, p_real)
is_real <- c(rep(FALSE, 90), rep(TRUE, 10))
cat("Raw < 0.05:", sum(all_p < 0.05), "total (",
sum(all_p < 0.05 & !is_real), "false positives,",
sum(all_p < 0.05 & is_real), "true positives)\n")Raw < 0.05: 14 total ( 5 false positives, 9 true positives)
cat("Bonferroni:", sum(p.adjust(all_p, "bonferroni") < 0.05), "\n")Bonferroni: 2
cat("BH FDR: ", sum(p.adjust(all_p, "BH") < 0.05), "\n")BH FDR: 6
Clinical trial (5 endpoints): Bonferroni, or better, pre-specify one primary endpoint and treat the rest as secondary. GWAS: BH FDR, or the conventional genome-wide significance threshold of 5 × 10⁻⁸ (which is effectively a very conservative Bonferroni for ~1 million independent SNPs).
4.7.3 Exercise 3 (Open-ended)
Estimated time: 15–30 minutes, depending on your data
Find a published paper in your field that reports multiple hypothesis tests. Does it apply any multiple testing correction? Is that correction appropriate for the study design? Write a short paragraph critique, noting what the authors did and what you would have done differently.
4.8 Comprehension Check
Estimated time: ~10 minutes
- A study reports p = 0.04. State the correct interpretation of this value in one sentence.
- What is a Type I error? What probability does \(\alpha = 0.05\) set for this error?
- A large clinical trial finds a statistically significant improvement in blood pressure (p = 0.001, mean difference = 0.5 mmHg). Is this finding clinically important? What additional information do you need to decide?
- You test 500 genes for differential expression. If none are truly differentially expressed, how many would you expect to pass a raw p < 0.05 threshold by chance?
- When would you prefer Bonferroni over FDR correction, and vice versa?
If the null hypothesis were true (no real effect), the probability of observing a test statistic as extreme as this one or more extreme is 0.04 (4%). It does not mean there is a 4% chance the null is true.
A Type I error is incorrectly rejecting a null hypothesis that is actually true (a false positive). Setting \(\alpha = 0.05\) means we accept a 5% chance of making this error on any single test.
Statistical significance does not equal clinical importance. A 0.5 mmHg reduction in blood pressure is almost certainly not clinically meaningful: typical antihypertensive treatments aim for 5–10 mmHg reductions. You need the 95% CI for the difference (to see the range of plausible effect sizes) and a clinical judgement about the minimum relevant effect in this context.
\(500 \times 0.05 = 25\) false positives are expected by chance alone. This is why multiple testing corrections are essential in omics studies and anywhere you are testing hundreds or thousands of hypotheses simultaneously.
Bonferroni: when any false positive is very costly: clinical trial with pre-specified endpoints, regulatory decision-making, confirmatory studies. FDR (BH): when you are screening many hypotheses and can tolerate some false discoveries in exchange for more power: genomics, proteomics, metabolomics, any exploratory high-dimensional study.
4.9 How to Report
In a Methods section:
“A two-sample Welch’s t-test was used to compare [outcome] between [group A] and [group B]. The significance threshold was set at α = 0.05. Where multiple comparisons were made, p-values were adjusted using the Benjamini–Hochberg method to control the false discovery rate.”
In a Results section:
“Mean [outcome] was X.XX (SD Y.YY) in [group A] and X.XX (SD Y.YY) in [group B]; the difference was [significant / not significant] (t(df) = X.XX, p = .YYY, 95% CI for difference: L.LL to U.UU).”
Always include:
- Means and SDs (or medians and IQRs) for both groups
- Test statistic with degrees of freedom (t(df) = …)
- Exact p-value (not “p < 0.05”; use “p < 0.001” when p is very small)
- 95% CI for the difference
Common errors:
- Writing “p = 0.000”; use “p < 0.001” instead
- Reporting only “p < 0.05” without the actual value
- Omitting the CI: this is often more informative than the p-value
- Writing “the result was significant” without giving the numbers
4.10 Further Reading
- Bland and Altman (1994): BMJ “Statistics notes” series: short, accessible articles on hypothesis testing and p-values
- Nature Methods Points of Significance: authoritative short articles on p-values, power, and replication in biology
- Benjamini and Hochberg (1995): the original FDR paper; readable and worth understanding