Activity: Describing Your Data
Mean, spread, counting correctly, and group summaries
Worksheet: Describing Your Data
How to use this worksheet
Work through each part in order, at your own pace. Type every line of code yourself into a plain R script — do not copy-paste. Blocks marked ▶ Run this are code you should type and execute. Blocks marked ✏️ Your turn ask you to write, modify, or answer something. Boxes marked 🚀 If you finish early are optional bonus material.
Part 1 · Load and split the data
▶ Run this in your Script:
library(tidyverse)
pine_df <- read_csv("data/pine_needles.csv")
shady_lengths <- pine_df |> filter(n_s == "n") |> pull(length_mm)
sunny_lengths <- pine_df |> filter(n_s == "s") |> pull(length_mm)✏️ Your turn: How many needles are in shady_lengths? ________________________
In sunny_lengths? ________________________
Part 2 · Mean and median
▶ Run this:
mean(shady_lengths)
median(shady_lengths)
mean(sunny_lengths)
median(sunny_lengths)✏️ Your turn: For each side, is the mean close to the median, or far from it? What would a big gap suggest about the shape of the data?
Shady — mean vs. median: ________________________
Sunny — mean vs. median: ________________________
Part 3 · Variance and standard deviation
▶ Run this:
var(shady_lengths)
sd(shady_lengths)
var(sunny_lengths)
sd(sunny_lengths)✏️ Your turn: Which side has more spread in its needle lengths — shady or sunny?
How do you know?
______________________________________________________________________
Part 3b · Quantiles, range, and IQR
▶ Run this:
range(shady_lengths)
max(shady_lengths) - min(shady_lengths)
quantile(shady_lengths)
IQR(shady_lengths)✏️ Your turn: Compute the same four things for sunny_lengths. Which side has the wider IQR?
# Write your code here:
Wider IQR: ________________________
🚀 If you finish early: Confirm that IQR(shady_lengths) equals quantile(shady_lengths, 0.75) - quantile(shady_lengths, 0.25) by computing both and comparing.
Part 4 · The length() trap
▶ Run this:
x <- c(4, 3, NA, 7, NA)
length(x)
sum(is.na(x))
sum(!is.na(x))✏️ Your turn: Explain in your own words why length(x) gives the wrong answer for a sample size when there are missing values.
________________________________________________________
▶ Run this:
wrong_n <- length(x)
wrong_se <- sd(x, na.rm = TRUE) / sqrt(wrong_n)
right_n <- sum(!is.na(x))
right_se <- sd(x, na.rm = TRUE) / sqrt(right_n)
wrong_se
right_se✏️ Your turn: Are wrong_se and right_se the same number? Why or why not? ________________________
🚀 If you finish early: Add a third NA to x and rerun both calculations. What happens to wrong_se and right_se this time?
Part 5 · Standard error, on real data
▶ Run this:
n_shady <- sum(!is.na(shady_lengths))
n_sunny <- sum(!is.na(sunny_lengths))
se_shady <- sd(shady_lengths) / sqrt(n_shady)
se_sunny <- sd(sunny_lengths) / sqrt(n_sunny)
se_shady
se_sunny✏️ Your turn: In one sentence, explain the difference between what SD tells you and what SE tells you.
________________________________________________________
Part 6 · All stats, one group, base R
▶ Run this:
n_l <- sum(!is.na(shady_lengths))
mean_l <- mean(shady_lengths, na.rm = TRUE)
med_l <- median(shady_lengths, na.rm = TRUE)
sd_l <- sd(shady_lengths, na.rm = TRUE)
se_l <- sd_l / sqrt(n_l)
cat("n =", n_l, " Mean =", round(mean_l, 2),
" Median =", round(med_l, 2),
" SD =", round(sd_l, 2), " SE =", round(se_l, 2), "\n")✏️ Your turn: Copy this block and adapt it to compute the same five numbers for sunny_lengths.
# Write your code here:
Part 6b · skimr — a fast full-dataset overview
▶ Run this (run install.packages("skimr") first, one time only, if you haven’t already):
library(skimr)
skim(pine_df)✏️ Your turn: Find length_mm in the skim() output. Do its mean and SD match what you calculated by hand in Parts 2 and 3?
Y / N
✏️ Your turn: What does skim() show you for a character column (like group or n_s) that it doesn’t show for a numeric one?
____________________________________
Part 7 · Tidy group stats — group_by() + summarize()
▶ Run this:
side_stats_df <- pine_df |>
group_by(n_s) |>
summarize(
n = sum(!is.na(length_mm)),
mean_mm = round(mean(length_mm, na.rm = TRUE), 2),
med_mm = round(median(length_mm, na.rm = TRUE), 2),
sd_mm = round(sd(length_mm, na.rm = TRUE), 2),
se_mm = round(sd_mm / sqrt(n), 2)
)
side_stats_df✏️ Your turn: Do the mean_mm and se_mm values match the ones you calculated by hand in Parts 2 and 5?
Y / N
✏️ Your turn: Now group by group (the field team) instead of n_s, and compute the same five statistics.
# Write your code here:
🚀 If you finish early: Group by both group and n_s at once (group_by(group, n_s)). How many rows does the result have, and why?
Part 7b · Bringing back case_when() from Wrangling Your Data
▶ Run this:
size_class_stats_df <- pine_df |>
mutate(
size_class = case_when(
length_mm < 16 ~ "short",
length_mm < 20 ~ "medium",
TRUE ~ "long"
)
) |>
group_by(size_class) |>
summarize(
n = sum(!is.na(length_mm)),
mean_mm = round(mean(length_mm, na.rm = TRUE), 2),
se_mm = round(sd(length_mm, na.rm = TRUE) / sqrt(n), 2)
)
size_class_stats_df✏️ Your turn: Which size_class has the largest n?
Does that match what you’d expect from the cutoffs (16 and 20)?
__________________________________________________________________
Part 8 · Save your summary table
▶ Run this:
write_csv(side_stats_df, "data/pine_needles_side_stats.csv")Check your data/ folder — the summary table should be there.
Part 9 · Review and checkpoint
At this point you can:
✏️ Your turn — before you move on: Run your whole script top to bottom.
Ran cleanly? Y / N
— if not, the error was: ________________________
📤 What to turn in before next class
Upload both of these to the course management system:
- Your code — the
scripts/folder (or just03_descriptive_stats.R) - This worksheet, with your written answers
Getting unstuck
When code breaks — and it will, that is normal:
- Read the error message out loud. R usually names the line and the problem.
- Check the usual suspects: did you run
library(tidyverse)? Spelling? Did you forgetna.rm = TRUE? ?function_nameopens the built-in help page.- Bring the exact error (copy-paste it) to class or office hours.
💡 Key idea: Every working scientist googles error messages daily. Getting stuck is not failing — it is the job.