Activity: Probability & Inference
From the normal curve to the confidence interval
Worksheet: Probability & Inference
Part 1 Β· Setup and Data
βΆ Run this:
# ββ Part 1.1 Β· Setup and Data βββββββββββββββββββββββββββββββββββββββββββ
library(janitor)
library(tidyverse)
g_df <- read_csv("data/gray_I3_I8.csv")
i3_df <- g_df %>% filter(lake == "I3")
head(g_df)βΆ Run this β the three numbers everything today depends on:
# ββ Part 1.2 Β· Setup and Data βββββββββββββββββββββββββββββββββββββββββββ
stats_df <- g_df %>%
group_by(lake) %>%
summarize(
mean_length = mean(length_mm, na.rm = TRUE),
sd_length = sd(length_mm, na.rm = TRUE),
n = sum(!is.na(length_mm)),
se_length = sd_length / sqrt(n),
.groups = "drop"
)
stats_dfβοΈ Your turn: Which lake has more fish measured? Which has the larger mean length? Which has the larger SD?
________________________
A tool you will reuse all semester
You just typed out mean, sd, n and se by hand. You will want those same four numbers in every analysis from here on β so letβs package them into a function and never type that block again.
This one is a tool, not a lesson β it is already filled in for you in the script skeleton. You do not have to type it. Read it, run it, and then use it.
{ variable } (the βcurly-curlyβ braces) is what lets you hand the function a bare column name like length_mm instead of a quoted string. It is the one piece of syntax here worth remembering.
βΆ Run this β define it once:
# ββ Part 1.3 Β· A reusable summary function ββββββββββββββββββββββββββββββ
summary_stats <- function(data, variable) {
data %>%
summarize(
n = sum(!is.na({{ variable }})),
mean = mean({{ variable }}, na.rm = TRUE),
sd = sd({{ variable }}, na.rm = TRUE),
se = sd / sqrt(n),
ci_lower = mean - qt(0.975, df = n - 1) * se,
ci_upper = mean + qt(0.975, df = n - 1) * se,
.groups = "drop"
) %>%
mutate(across(c(mean, sd, se, ci_lower, ci_upper),
~ round_half_up(.x, 2)))
}βΆ Run this β now use it. Three ways, no retyping:
# ββ Part 1.4 Β· A reusable summary function ββββββββββββββββββββββββββββββ
g_df %>% group_by(lake) %>% summary_stats(length_mm) # per lake
g_df %>% summary_stats(length_mm) # both lakes pooled
g_df %>% group_by(lake) %>% summary_stats(mass_g) # a different columnβοΈ Your turn: Compare the first result to stats_df from Part 1.2. Do the mean, sd and se columns match?
________________________
βοΈ Your turn: The function also returns ci_lower and ci_upper β a 95% confidence interval. We have not explained those yet. That is Part 6. For now, just notice they are there.
Part 2 Β· The Normal Distribution
π Read more: Whitlock & Schluter Ch. 10 (The Normal Distribution); Gotelli & Ellison Ch. 2.
A normal distribution is fully described by just two numbers: its mean (ΞΌ) and its standard deviation (Ο). Letβs draw the normal curve that matches our fish and see how well it fits.
βΆ Run this:
# ββ Part 2.1 Β· The Normal Distribution ββββββββββββββββββββββββββββββββββ
i3_mean <- mean(i3_df$length_mm, na.rm = TRUE)
i3_sd <- sd(i3_df$length_mm, na.rm = TRUE)
i3_df %>%
ggplot(aes(x = length_mm)) +
geom_histogram(aes(y = after_stat(density)), binwidth = 10,
fill = "lightblue", colour = "white") +
stat_function(fun = dnorm, args = list(mean = i3_mean, sd = i3_sd),
colour = "red", linewidth = 1) +
labs(x = "Length (mm)", y = "Density")βοΈ Your turn: Where does the red curve sit above the blue bars, and where does it sit below them? Look especially at the right-hand side.
________________________
Part 3 Β· Z-Scores β Putting Everything on One Ruler
π Read more: Whitlock & Schluter Ch. 10 β standardizing a value.
There is a different normal curve for every ΞΌ and Ο. Standardizing rescales any of them into the same units: standard deviations from the mean.
βΆ Run this:
# ββ Part 3.1 Β· Z-Scores βββββββββββββββββββββββββββββββββββββββββββββββββ
i3_df <- i3_df %>%
mutate(z_score = (length_mm - i3_mean) / i3_sd)
i3_df %>%
select(lake, length_mm, z_score) %>%
head()
i3_df %>%
ggplot(aes(x = z_score)) +
geom_histogram(binwidth = 0.25, fill = "blue", alpha = 0.7) +
labs(x = "z (SDs from the mean)", y = "Count")βοΈ Your turn: Compare this histogram to the one in Part 2. What changed β the shape, or just the axis numbers?
________________________
βΆ Run this β what fraction of our fish really are within 1 SD?
# ββ Part 3.2 Β· Z-Scores βββββββββββββββββββββββββββββββββββββββββββββββββ
within_1sd <- mean(abs(i3_df$z_score) <= 1, na.rm = TRUE)
round_half_up(100 * within_1sd, 1)βοΈ Your turn: A perfect normal distribution gives 68%. What did you get, and is the gap big or small?
________________________
Part 4 Β· Area Under the Curve = Probability
π Read more: Whitlock & Schluter Ch. 5 (Probability) and Ch. 10.
The whole point of a density curve is that area under it is probability. pnorm() gives us that area without any integration.
| Function | You give it | You get back |
|---|---|---|
pnorm(z) |
a z | the area to the left |
qnorm(p) |
an area | the z that cuts it off |
dnorm(z) |
a z | the height of the curve |
βΆ Run this:
# ββ Part 4.1 Β· Area Under the Curve βββββββββββββββββββββββββββββββββββββ
pnorm(1.96) # area to the LEFT of 1.96
1 - pnorm(1.96) # area to the RIGHT
qnorm(0.975) # the z with 97.5% to its left - the inverse of pnormβΆ Run this β the worked example from lecture: how rare is a 300 mm grayling?
# ββ Part 4.2 Β· Area Under the Curve βββββββββββββββββββββββββββββββββββββ
z_300 <- (300 - i3_mean) / i3_sd
round_half_up(z_300, 2) # should be about 1.22
round_half_up(100 * (1 - pnorm(z_300)), 1) # % of fish LONGER than 300 mmβΆ Run this β and now run it backwards, from a probability to a length:
# ββ Part 4.3 Β· Area Under the Curve βββββββββββββββββββββββββββββββββββββ
z_top5 <- qnorm(0.95)
length_top5 <- i3_mean + z_top5 * i3_sd
round_half_up(length_top5, 1)βοΈ Your turn: In one sentence, what does that last number mean to someone fishing lake I3?
________________________
βοΈ Your turn: Use pnorm() to find the percentage of I3 fish shorter than 240 mm. Convert 240 to a z-score first.
# ββ Part 4.4 Β· Area Under the Curve βββββββββββββββββββββββββββββββββββββ
# Write your code in the R script:π If you finish early: Shade the tail you just calculated.
# ββ Part 4.5 Β· Area Under the Curve βββββββββββββββββββββββββββββββββββββ
i3_df %>%
ggplot(aes(x = length_mm)) +
geom_density(fill = "lightblue", alpha = 0.5) +
geom_vline(xintercept = 300, colour = "red", linetype = "dashed")Part 5 Β· Checking the Assumption
π Read more: Whitlock & Schluter Ch. 13 (Handling Violations of Assumptions).
Every number in Part 4 assumed the fish are normally distributed. Now we test that.
Watch the hypotheses β they run backwards from what you expect:
- Hβ: the data ARE normal
- Hβ: the data are NOT normal
- so p < 0.05 means NOT normal β a small p-value here is bad news
βΆ Run this:
# ββ Part 5.1 Β· Checking the Assumption ββββββββββββββββββββββββββββββββββ
shapiro.test(i3_df$length_mm)
i3_df %>%
ggplot(aes(sample = length_mm)) +
stat_qq() +
stat_qq_line(colour = "red") +
labs(x = "Theoretical quantiles", y = "Sample quantiles")βοΈ Your turn: What is the p-value, and what does it say about normality? On the Q-Q plot, where do the points leave the red line?
________________________
βΆ Run this β so how wrong was our 300 mm answer? Compare theory to the actual fish:
# ββ Part 5.2 Β· Checking the Assumption ββββββββββββββββββββββββββββββββββ
round_half_up(100 * (1 - pnorm(300, i3_mean, i3_sd)), 1) # normal theory
round_half_up(100 * mean(i3_df$length_mm > 300, na.rm = TRUE), 1) # actual fishβοΈ Your turn: How far apart are those two numbers? Why does it matter that the disagreement is out in the tail rather than in the middle?
________________________
π If you finish early: Run shapiro.test() on lake I8 as well. Is it normal? Does the Q-Q plot agree with the p-value?
# ββ Part 5.3 Β· Checking the Assumption ββββββββββββββββββββββββββββββββββ
# Write your code in the R script:Part 6 Β· SD, SE, and Confidence Intervals
π Read more: Whitlock & Schluter Ch. 4 (Estimating with Uncertainty) β this is the chapter that separates SD from SE.
SD describes how much individual fish vary. SE describes how much a sample mean would vary from study to study. They answer different questions and must not be swapped.
βΆ Run this:
# ββ Part 6.1 Β· SD, SE, and Confidence Intervals βββββββββββββββββββββββββ
i3_n <- sum(!is.na(i3_df$length_mm))
i3_se <- i3_sd / sqrt(i3_n)
tibble(
n = i3_n,
SD = round_half_up(i3_sd, 1),
SE = round_half_up(i3_se, 1)
)βοΈ Your turn: If we measured 4Γ as many fish, which of those two numbers would shrink, and by how much?
________________________
βΆ Run this β build a 95% CI by hand, the way the formula reads: \(\bar{y} \pm t \cdot SE\)
# ββ Part 6.2 Β· SD, SE, and Confidence Intervals βββββββββββββββββββββββββ
t_crit <- qt(0.975, df = i3_n - 1) # two-tailed, 95%
t_crit
ci_lower <- i3_mean - t_crit * i3_se
ci_upper <- i3_mean + t_crit * i3_se
round_half_up(c(ci_lower, ci_upper), 1)βΆ Run this β now check yourself two ways: against Rβs t.test(), and against the function you wrote back in Part 1.3.
# ββ Part 6.3 Β· SD, SE, and Confidence Intervals βββββββββββββββββββββββββ
t.test(i3_df$length_mm)$conf.int # R's answer
i3_df %>% summary_stats(length_mm) # your function's answerβοΈ Your turn: Do all three intervals β yours by hand, t.test(), and summary_stats() β agree?
________________________
π‘ That is the payoff. The
ci_lower/ci_uppercolumns were in your functionβs output back in Part 1.4, before you knew what they meant. Now you do β and you get them on any grouped dataset for free:g_df %>% group_by(lake) %>% summary_stats(length_mm)One line, and every lake gets a mean, an SE, and a confidence interval.
βοΈ Your turn: Write the I3 interval as a sentence you could put in a paper.
________________________
βΆ Run this β why t and not 1.96? Take a small sample and watch the multiplier change:
# ββ Part 6.4 Β· SD, SE, and Confidence Intervals βββββββββββββββββββββββββ
set.seed(42)
small_sample <- i3_df %>% slice_sample(n = 10)
s_mean <- mean(small_sample$length_mm)
s_se <- sd(small_sample$length_mm) / sqrt(10)
tibble(
method = c("z (wrong here)", "t (correct)"),
crit = round_half_up(c(1.96, qt(0.975, df = 9)), 3),
lower = round_half_up(s_mean - crit * s_se, 1),
upper = round_half_up(s_mean + crit * s_se, 1),
width = round_half_up(upper - lower, 1)
)βοΈ Your turn: Which interval is wider, and why is the wider one the honest one?
________________________
The interpretation to get right
β βThere is a 95% chance ΞΌ is in my interval.β β β95% of intervals built this way capture ΞΌ.β
ΞΌ is a fixed number. The interval is what is random.
Part 7 Β· One-Sample t-Test
π Read more: Whitlock & Schluter Ch. 11 (Inference for a Normal Population) and Ch. 6 (Hypothesis Testing).
A CI asks where is ΞΌ? A t-test asks is ΞΌ equal to some specific value?
βΆ Run this β do I3 fish differ from a historical mean of 260 mm?
# ββ Part 7.1 Β· One-Sample t-Test ββββββββββββββββββββββββββββββββββββββββ
t.test(i3_df$length_mm, mu = 260)βοΈ Your turn: Fill these in from the output.
H0 = ______________________________________________
Ha = ______________________________________________
t = __________ df = __________
p-value = __________
95% CI = __________ to __________
Decision at Ξ± = 0.05: ________________________________________
βοΈ Your turn: Does the confidence interval contain 260? Explain how that answer and the p-value are telling you the same thing.
________________________
Part 8 Β· Two-Sample t-Test
π Read more: Whitlock & Schluter Ch. 12 (Comparing Two Means) and Ch. 6 on one- vs. two-tailed tests.
Formulate the hypotheses first, for the question βAre fish in Lake I8 longer than fish in Lake I3?β
H0 = ________________________________________________________
Ha = ________________________________________________________
This question has a direction, so it is one-tailed. R orders lake alphabetically and tests I3 β I8, so βI8 is longerβ means that difference is less than zero.
βΆ Run this:
# ββ Part 8.1 Β· Two-Sample t-Test ββββββββββββββββββββββββββββββββββββββββ
t.test(length_mm ~ lake, data = g_df, alternative = "less")βοΈ Your turn: What do you conclude? Report the difference in mm, not just the p-value.
________________________
βοΈ Your turn: The output says βWelch Two Sample t-testβ. Look back at the SDs in Part 1.2. Why is Welch the right default here?
________________________
π If you finish early: Rerun with alternative = "two.sided". Does the p-value change? Does the conclusion? Which should you have chosen before seeing the data?
# ββ Part 8.2 Β· Two-Sample t-Test ββββββββββββββββββββββββββββββββββββββββ
# Write your code in the R script:Review and checkpoint
At this point you can:
π€ What to turn in before next class
- Your script β
scripts/05_probability_inference.R - This worksheet, with your written answers
Readings
Before next class β chapter PDFs are on Canvas:
- Whitlock & Schluter β Ch. 10, The Normal Distribution; Ch. 4, Estimating with Uncertainty; Ch. 6, Hypothesis Testing. If you read only one, read Ch. 4
- Gotelli & Ellison β Ch. 2, Random Variables and Probability Distributions; Ch. 4, Framing and Testing Hypotheses
| Worksheet part | Read |
|---|---|
| Parts 1-2 β summary stats, the normal curve | W&S Ch. 3, Ch. 10 |
| Parts 3-4 β z-scores, area as probability | W&S Ch. 10; Gotelli Ch. 2 |
| Part 5 β checking normality | W&S Ch. 13 |
| Part 6 β SD, SE, confidence intervals | W&S Ch. 4; Ch. 11 |
| Part 7 β one-sample t-test | W&S Ch. 11, Ch. 6 |
| Part 8 β two-sample t-test | W&S Ch. 12; Gotelli Ch. 4 |
If you want more: R4DS Β§13 β Numbers for mean(), sd() and rounding, and Β§9 β Layers for the plots.