PSet 2

Who benefits when kangaroo rats are excluded?

In 1977, ecologists in the Arizona desert built fences with doors too small for one animal, the kangaroo rat, and then counted who showed up every month for decades.

A kangaroo rat (Dipodomys) at its burrow.

Your question: when the biggest seed-eater is fenced out, are other rodents caught more often? The answer is in 35,606 rows of data, but only if you count fairly.

The essentials
  • Due: Sunday, November 15, 2026, 11:59 PM PT, on Bruin Learn. (Released Tuesday, November 3.)
  • Chapters: 1–19.
  • Download: starter project and data. The report template has one section per task.
  • Already downloaded the zip earlier? The data are unchanged. Just save the updated report template and references.bib into your project folder, next to the .Rproj file.
  • Submit: your zipped project folder, PSet2_LastName.zip (see the checklist).
  • Grading: 15 items × 2 pts = 30. Each item is scored 2 / 1 / 0 (complete / partial / missing).
  • AI: not allowed (per the syllabus). Discussing ideas is fine; your code and words must be your own.

What you need to know

Term Meaning
plot A fenced 50 × 50 m patch. 16 plots: 8 control (all rodents enter), 8 exclosure (kangaroo rats DM, DO, DS kept out).
census One monthly visit. Each sampled plot gets 49 traps for one night = 49 trap-nights.
capture One row of captures.csv: one animal caught once.
capture rate 100 * captures / trap_nights. We compare rates, not counts.

Why rates? A plot visited twice as often catches twice as many animals:

Plot Visits Trap-nights Captures Rate
A 10 490 10 2.0
B 5 245 5 2.0

Same rate, different counts. Real plots get 8–13 visits a year.

Why zeros matter: a visit that caught nothing is a real 0. A visit that never happened is not a 0.

File One row is… Key columns
captures.csv one capture census, plot, species, mass_g, hindfoot_mm
species.csv one species species, common_name
plots.csv one plot plot, treatment
surveys.csv one plot × visit (incl. unsampled) census, plot, year, sampled, trap_nights, protocol_ok

Start: open PSet2_Portal.Rproj → rename the .qmd → Render once → write a 2–3 sentence prediction at the top.

Task 1 — Join the tables (4 pts)

  1. (2 pts) Make a table of the number of rows and columns in each file, and count the missing values in mass_g. [nrow(), ncol(), knitr::kable(), is.na()]
    Write: why not replace a missing mass with 0?
  2. (2 pts) Build captures_joined: add each capture’s species name, treatment, and year (matching surveys on both census and plot). Add a column rodent_group: "Kangaroo rat" for DM/DO/DS, "Other rodent" otherwise. [left_join(), mutate(), if_else()]
    Write: match surveys on plot only instead. How many rows do you get? Why?

Check: captures 35,606 · surveys 5,056 · species 21 · plots 16 · missing mass 810 · captures_joined 35,606.

Warning

Species code "NA" is a real species. The starter reads files with na = "" so it isn’t treated as missing.

An audit table is just a small tibble you build by hand:

tibble(table = c("captures", "species"),
       rows  = c(nrow(captures), nrow(species)))

Extend it to all four files and add a columns column.

For the join, the first step looks like this; add the other two yourself:

captures_joined <- captures |>
  left_join(species, by = "species")

Keep only the survey columns you need (census, plot, year) before joining. Always check nrow() after a join: duplicate keys in the right table add rows (Chapter 10).

Task 2 — Count captures, keeping the zeros (4 pts)

Goal: one row per sampled visit × rodent group, with zero-capture visits kept.

  1. (2 pts) Build survey_base yourself: keep only visits that were sampled, passed the protocol check, and had traps set (see the sampled, protocol_ok, and trap_nights columns; 1 means yes); then add each plot’s treatment. [filter(), left_join()]
    Then run the rest of the pipeline from the box below.
    Write: why must an unsampled visit be removed rather than counted as zero? Explain the 3 numbered comments in the box code in your own words.
  2. (2 pts) Combine capture_counts with survey_base using three different join types and report the row count of each. [left_join(), inner_join(), full_join(), nrow()]
    Write: which visits does the inner join drop? Why is the left join the right choice?

Check: survey_base 4,893 rows · census_rates 9,786 rows · 2,887 zeros.

Write survey_base yourself first (check: 4,893 rows). Then:

capture_counts <- captures_joined |>
  group_by(census, plot) |>
  summarise(kangaroo = sum(rodent_group == "Kangaroo rat"),
            other    = sum(rodent_group == "Other rodent"),
            .groups = "drop")

census_rates <- survey_base |>                          # 1. Start from visits
  left_join(capture_counts, by = c("census", "plot")) |>
  mutate(kangaroo = replace_na(kangaroo, 0),            # 2. Fill counts with 0
         other    = replace_na(other, 0)) |>
  pivot_longer(c(kangaroo, other),
               names_to = "rodent_group", values_to = "n_captures") |>
  mutate(capture_rate = 100 * n_captures / trap_nights) # 3. Rate, not count

Task 3 — Compare treatments by year (4 pts)

  1. (2 pts) Build three tables:
    • plot_year: the mean capture_rate for each year, plot, treatment, and rodent group. [group_by(), summarise(), mean()]
    • annual_rates: the mean of those across plots, for each year, treatment, and rodent group. [group_by(), summarise()]
    • difference_table: other rodents only, with columns year, control, exclosure, and difference (exclosure minus control). Display it in the chunk labelled tbl-difference. [filter(), select(), pivot_wider(), mutate(), knitr::kable()]
  2. (2 pts) Write: what does a positive difference mean (units)? Which years are largest and smallest?

Check: annual_rates 108 rows · difference_table 27 rows.

  • group_by() the columns that define one row of the result, then summarise(). Add .groups = "drop" to avoid leftover grouping.
  • annual_rates starts from plot_year, not from census_rates.
  • For difference_table, keep only year, treatment, and the rate before widening; treatment supplies the new column names.

Skip the select() and pivot_wider() gives a table full of NAs (Chapter 9).

Task 4 — Explore (6 pts)

  1. (2 pts) p_trend: plot annual_rates over time as lines, colored by treatment, with one panel per rodent group. Then make an interactive version. [ggplot(), geom_line(), facet_wrap(), plotly::ggplotly()]
    Write: did the fences keep kangaroo rats out? What happened to other rodents?
  2. (2 pts) Make two of: (a) a sorted bar chart of captures per species; (b) the body-mass distribution of DM, PB, PP; (c) hindfoot length vs. mass for those species. [count(), geom_col(), geom_boxplot(), geom_point(), drop_na()]
    Write: one sentence under each.
  3. (2 pts) Ask one follow-up question from your trend plot; answer it with a table or plot.
    Write: Question / Observation (with a number) / What I’d check next.
p_trend <- ggplot(annual_rates, aes(x = ___, y = ___, color = ___)) +
  geom_line() +
  facet_wrap(~ ___)
    1. count(common_name), then reorder(common_name, n) to sort bars.
  • (b, c) filter(species %in% c("DM", "PB", "PP")) and drop_na(mass_g).
  • Follow-up idea: in the year with the biggest gap, which species were caught most in each treatment? filter() + count(treatment, common_name).

Task 5 — Final figure (4 pts)

Chunk label: fig-main. Other-rodent capture rate over time, both treatments.

  1. (2 pts) Use the same two treatment colors as before, plus a second cue (linetype or shape) so it works without color. [scale_color_manual(), aes(linetype = )]
  2. (2 pts) Add a title that states the finding, axis labels with units, and a caption with the years and data source. Export it as PDF and PNG with a set width and height. [labs(), #| fig-cap:, ggsave()]
scale_color_manual(values = c(control = "#0072B2", exclosure = "#D55E00"))

ggsave(here::here("figures", "figure_1.png"), plot = figure_1,
       width = 7, height = 4.5, dpi = 300)

Task 6 — Write it up (4 pts)

  1. (2 pts) Methods: in a short paragraph, say which data you used and how you calculated the capture rate (in words). Cite the two references in references.bib, and refer to your table and figure in the text. [[@ernest2009; @portaldata], @tbl-difference, @fig-main]
  2. (2 pts) Conclusion: 100–150 words. Answer the question with numbers + one limitation.

Check: HTML shows “Table 1”, “Figure 1”, and a reference list (no ?@).

Citations and cross-references look like this (a different example, so adapt it):

Penguin data come from @gorman2014. Bill length differs
among species (@tbl-bills, @fig-bills).

Chapter 19 covers citations and cross-references.

Task 7 — Reproducible (4 pts)

  1. (2 pts) Restart R and render. The report runs top to bottom and recreates figures/figure_1.pdf and .png.
  2. (2 pts) In the Console, set up a package record for the project, then save it after a successful render. [renv::init(), renv::snapshot(); see Chapter 18]

Submission checklist