fishery$big_trip <-ifelse(fishery$tonnes >=50, "major", "routine")if (any(fishery$tonnes >100, na.rm =TRUE)) {message("At least one landing exceeds 100 t — check port log")}table(fishery$big_trip)
ac_mat <-as.matrix(xtabs(detections ~ animal_id + reef_zone, data = acoustic))apply(ac_mat, 2, sum) # detections summed per reef zone
Crest Lagoon Slope
26 22 43
Q7 · lapply() lists
by_site <-split(reef, reef$site) # list of data frames byrichness <-lapply(by_site, function(df) { # function runs on each sitelength(unique(df$species)) # count distinct species names})richness # list of one integer per
$East
[1] 5
$North
[1] 5
$South
[1] 5
unlist(richness)
East North South
5 5 5
Ex 2.
by_year <-split(fishery$tonnes, fishery$year) # list of tonne vectors peryear_sums <-lapply(by_year, sum)unlist(year_sums)
by_animal <-split(acoustic$detections, acoustic$animal_id) # list of detection vectorsvia_sapply <-sapply(by_animal, max) # simplified numeric vector if uniformvia_lapply <-lapply(by_animal, max) # always a liststr(via_sapply) # inspect simplified structure
Named int [1:3] 7 5 7
- attr(*, "names")= chr [1:3] "A101" "A102" "A103"
str(via_lapply) # compare: list of scalars
List of 3
$ A101: int 7
$ A102: int 5
$ A103: int 7
Q9 · vapply() safe types
by_receiver <-split(acoustic$detections, acoustic$receiver) # group detectionstotals <-vapply( # vapply needs FUN.VALUE prototype by_receiver, sum,FUN.VALUE =numeric(1) # each element must be length-1)sort(totals, decreasing =TRUE) # which receivers saw most pings
agg_zone <-aggregate( # formula: response ~ grouping detections ~ reef_zone, # sum detections per zonedata = acoustic,FUN = sum)agg_zone # one row per zone
b1 <- biolog[biolog$burst_id ==1, ]pitch <- b1$pitch_deg # pitch vector in time ordersecs <- b1$secondsd_pitch <-mapply(function(a, b) abs(b - a), pitch[-length(pitch)], # earlier second pitch[-1]) # next secondhead(d_pitch) # first few step changes
ord <-order(fishery$year) # sort by year for pairingt <- fishery$tonnes[ord] # tonnes in year orderincrease <-mapply(function(prev, curr) curr > prev, t[-length(t)], t[-1]) # TRUE if this trip >head(increase)
[1] TRUE FALSE TRUE FALSE TRUE FALSE
Ex 2.
mapply(function(c, d) if (d >0) c / d elseNA_real_, reef$count, reef$depth_m)[1:6]
set.seed(20260721) # lock RNG for this questids <-unique(reef$transect_id) # all transect labelsaudit_ids <-sample(ids, size =4) # pick 4 transects without replacementreef[reef$transect_id %in% audit_ids, ] # rows selected for audit
transect_id site species count depth_m depth_class
1 T01 North Parrotfish 4 6.2 deep
2 T01 North Surgeonfish 12 6.2 deep
3 T01 North Grouper 1 6.2 deep
4 T01 North Snapper 7 6.2 deep
5 T01 North Damselfish 21 6.2 deep
11 T03 East Parrotfish 6 6.3 deep
12 T03 East Surgeonfish 13 6.3 deep
13 T03 East Grouper 4 6.3 deep
14 T03 East Snapper 5 6.3 deep
15 T03 East Damselfish 26 6.3 deep
26 T06 East Parrotfish 11 9.9 deep
27 T06 East Surgeonfish 10 9.9 deep
28 T06 East Grouper 8 9.9 deep
29 T06 East Snapper 8 9.9 deep
30 T06 East Damselfish 26 9.9 deep
56 T12 East Parrotfish 13 3.4 shallow
57 T12 East Surgeonfish 9 3.4 shallow
58 T12 East Grouper 8 3.4 shallow
59 T12 East Snapper 4 3.4 shallow
60 T12 East Damselfish 18 3.4 shallow
Q15 · order() and sorting logic
ord <-order(fishery$year, -fishery$tonnes) # year ascending, tonnes descendingfishery_sorted <- fishery[ord, ] # reorder all columns togetherhead(fishery_sorted, 8)
year species tonnes port big_trip kg
2 2018 Grouper 79.6 Mooloolaba major 79600
1 2018 Snapper 75.7 Cairns major 75700
4 2018 Shark 67.7 Cairns major 67700
3 2018 Tuna 23.5 Broome routine 23500
8 2019 Shark 85.1 Cairns major 85100
6 2019 Grouper 80.6 Cairns major 80600
5 2019 Snapper 58.0 Broome major 58000
7 2019 Tuna 53.4 Mooloolaba major 53400
Q16 · unique() & table()
unique(acoustic$animal_id) # how many tagged animals
# vectorised approach using ave() — mean pitch per burst_idcentred <- biolog$pitch_deg -ave(biolog$pitch_deg, biolog$burst_id, FUN = mean)# loop approach for same result (first 50 rows demo)loop_cent <- biolog$pitch_degfor (i in1:50) { bid <- biolog$burst_id[i] loop_cent[i] <- biolog$pitch_deg[i] -mean(biolog$pitch_deg[biolog$burst_id == bid])}all.equal(loop_cent, centred[1:50]) # methods should agree
ord <-order(fishery$year, fishery$species)t <- fishery$tonnes[ord]all.equal(cumsum(t), { out <-numeric(length(t)); for (i inseq_along(t)) out[i] <-sum(t[1:i]); out })
[1] TRUE
Q20 · Base R mini-capstone
library(ggplot2) # only for quick plot atac <- acoustic # working copyac$hot <- ac$detections >=5by_an <-aggregate(detections ~ animal_id, data = ac, FUN = sum) # totals per tagzone <-aggregate(reef_zone ~ animal_id, data = ac, FUN =function(x) unique(x)[1])merged <-merge(by_an, zone, by ="animal_id")ggplot(merged, aes(x =reorder(animal_id, detections), y = detections, fill = reef_zone)) +geom_col() +# bar chart of totalscoord_flip() +# readable animal labelslabs(title ="Detections per tagged animal", x =NULL, y ="Total detections")
Go deeper (open access)
These quests are a short path; the field already publishes free courses, scripts, and books. We paraphrase their goals below—follow the links for full material.
R for Reproducible Scientific Analysis (Carpentries) extends that to multi-step, reproducible pipelines—close to how we chain dplyr and save scripts in RStudio.
R for Data Science (2e) (Wickham, Çetinkaya-Rundel, Grolemund) is the standard free book for import → tidy → transform → plot; our dplyr track follows the same verb order (CC BY-NC-ND).
Advanced R (Wickham) explains why R behaves like a functional language and how map-style thinking fits data analysis—background for our purrr track.
Marine & ecological statistics (people & labs)
Seascape Models — Chris Brown (University of Tasmania) shares open teaching on predictive ecological modelling in R, with course notes and data on GitHub—quantitative marine decision-making with reproducible scripts.
Bayesian Data Analysis in Ecology (Korner-Nievergelt et al.) — free, evolving e-book with R/Stan examples; useful once regression and purrr-style replication make sense.
R Workshops @ UQ — long-running workshops from mathematical ecologists at the University of Queensland on R and applied modelling for researchers (SCIE3360 also stresses reproducible data science in R for life and environmental sciences).
Universities & field training (context, not enrollment)
University of St AndrewsStatistical Ecology MSc and incoming MSc guidance treat R as assumed tooling for modern applied statistics; CREEM runs structured stats training for St Andrews researchers.
University of Plymouth embeds statistics workshops in marine biology field courses and points students to R-focused texts via library guides (e.g. biologist-oriented R and reproducible research titles).
Max Planck Institute of Animal Behavior — AniMove runs open-source–focused training on movement, remote sensing, and R; recorded material is linked from Movebank teaching.
FIU — Simon Dedman’s open graduate stats / R materials cover tidying, SDMs, and causal modelling on marine datasets (see also reproducible reef analyses on Zenodo).
Galápagos — the Charles Darwin Foundation has run free community courses on statistics and R with local partners; the Galapagos Science Center supports interdisciplinary, data-heavy conservation research.
Functional tools (packages & references)
purrr (Henry & Wickham) — official reference for typed map functions used in our Intermediate purrr track.
data.table — documentation and vignettes for [i, j, by] syntax in our Advanced track.
Big Book of R — curated chapters linking base R, tidyverse, and workflow topics; handy index when you outgrow quests.
Self-paced ecology coding
Coding Club — Data Science for Ecologists (University of Edinburgh) — free, self-paced R tutorials and quizzes aimed at environmental scientists; good parallel practice alongside any quest level.
If you use AI tools while learning, treat these sources as the ground truth: run their code, compare output to ours, and cite them when you adapt their workflows.