Activity: Describing Your Data

Mean, spread, counting correctly, and group summaries

Hands-on worksheet: mean, median, variance, SD, SE, quantiles/range/IQR, the length() trap, a fast skimr overview, and group_by() + summarize() — including the case_when() categories from Wrangling Your Data.
Author

Bill Perry

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: ________________________
Tip

🚀 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? ________________________

Tip

🚀 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:

Tip

🚀 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: ________________________

Note

📤 What to turn in before next class

Upload both of these to the course management system:

  1. Your code — the scripts/ folder (or just 03_descriptive_stats.R)
  2. This worksheet, with your written answers

Getting unstuck

When code breaks — and it will, that is normal:

  1. Read the error message out loud. R usually names the line and the problem.
  2. Check the usual suspects: did you run library(tidyverse)? Spelling? Did you forget na.rm = TRUE?
  3. ?function_name opens the built-in help page.
  4. 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.