Show code
sitedat <-
read.csv(
"../../../RRSdata/00_RRS_dataCatalogStatus/00_RRS_siteMaster_allSites_data.csv"
)This page estimates parrotfish herbivory on TCRMP reefs and shows how herbivore abundance, biomass, and grazing intensity vary across sites over time. The workflow keeps the seven Scarus and Sparisoma species with published bite parameters, converts counts to biomass with species length-weight coefficients, and applies the bite-rate and bite-size equations of Mumby (2006) to compute the proportion of reef grazed per hour at each site. It produces four site-faceted time series (abundance, biomass, grazing intensity by site, and annual mean grazing intensity), each with a dashed reference line at the 2005 Caribbean-wide bleaching year, and one downloadable data product with matching metadata. The abundance and biomass conversions cover Scarus and Sparisoma only, so this page describes parrotfish grazing rather than total herbivory.
This page reads the TCRMP annual fish transect counts and the TCRMP site master, both from the TCRMP program, and the published parrotfish bite-rate, bite-size, and length-weight parameters described under herbivory parameters. The bite-rate and bite-size parameters exist for Scarus and Sparisoma only, and transect area changed over the record (60 m² before 2009, 100 m² from 2009 onward), which the grazing calculation accounts for. The derived per-transect counts, biomass, and grazing intensities computed here are available in the Downloads section below.
The site master lists each monitoring site with its island, depth, and the year it was added.
sitedat <-
read.csv(
"../../../RRSdata/00_RRS_dataCatalogStatus/00_RRS_siteMaster_allSites_data.csv"
)What happens here: the page reads the raw TCRMP annual fish transect counts, the input for every biomass and grazing calculation below.
fishcounts <-
read.csv(
"../../../RRSdata/data_TCRMP/TCRMP_fishCounts_allSites_2003_2023_data.csv"
)An optional site-and-year filter (keep annual surveys within the embargo window, and sites added no later than 2003) is kept below for reference. It is commented out, so the current run uses the full fish-count file as loaded.
# sitekp <-
# sitedat |>
# filter(program == "TCRMP" &
# yearadded <= maxyearadded) |>
# select(site)
#
# fishcounts <-
# fishcounts |>
# filter(Period == "Annual") |>
# filter(SampleYear >= minyear &
# SampleYear <= maxyear) |>
# filter(Location %in% sitekp$site)The bite-size equation uses a multiplier from Bruggemann et al. (1996).
bitesizemultiplyer = 0.0001The genus and species tables hold the bite-rate and bite-size parameters for Scarus and Sparisoma (Mumby 2006; Bruggemann et al. 1996).
genusinfo <- data.frame(
genus = c("Sparisoma", "Scarus"),
w = c(1, 1),
biterateparama = c(1088, 3329),
biterateparamb = c(17.12, 33),
bitesizeparamm = c(5.839, 4.013)
)
speciesinfo <- data.frame(
species = c(
"Scarus vetula",
"Scarus taeniopterus",
"Scarus iserti",
"Sparisoma aurofrenatum",
"Sparisoma rubripinne",
"Sparisoma chrysopterum",
"Sparisoma viride"
),
offset = c(0,
1196,
1714,
260,
142,
264,
56)
)The forklengths table records the median fork length for each size bin. The fish counts store size bins as range column names like “10-20”, so the code splits each range and takes its midpoint. The table holds two columns: the size bin and its median length.
sizebins <- colnames(fishcounts[, 9:19])
sizebins <- gsub("X", "", sizebins)
sizebins <- gsub("\\.", "-", sizebins)
forklengths <-
data.frame(sizebin = sizebins,
median = unlist(lapply(lapply(
strsplit(sizebins, "-"), as.numeric
), median)))
forklengths$median[2:length(forklengths$median)] <-
forklengths$median[2:length(forklengths$median)] - 0.5 # to match capstone median values Keep only the parrotfish species with known bite parameters, then reshape the size-bin columns into a tidy long form.
fishcounts <- fishcounts |>
filter(ScientificName %in% speciesinfo$species) |>
select(colnames(fishcounts[, 1:19])) |>
# as.data.frame()|>
pivot_longer(
cols = colnames(fishcounts[, 9:19]),
names_to = "sizebin",
values_to = "counts"
) |>
arrange(Location,
SampleYear,
SampleMonth,
Period,
Transect,
ScientificName,
sizebin)Clean the size-bin labels and rename the columns to short, consistent names.
Join each record to its median fork length from forklengths.
fishcounts <-
merge(forklengths, fishcounts, by = "sizebin", sort = T)The speciessizeconversions table holds the length-weight coefficients (a and b) for each herbivore species. It comes from the 2020 capstone folder file “Copy of Biomass_Size_Conversions.csv”.
speciessizeconversions <- data.frame(
sppname = c(
"Acanthurus bahianus",
"Acanthurus chirurgus",
"Acanthurus coeruleus",
"Kyphosus sectatrix",
"Microspathodon chrysurus",
"Scarus coelestinus",
"Scarus coelruleus",
"Scarus guacamaia",
"Scarus iserti",
"Scarus taeniopterus",
"Scarus vetula",
"Sparisoma atomarium",
"Sparisoma aurofrenatum",
"Sparisoma chrysopterum",
"Sparisoma radians",
"Sparisoma rubripinne",
"Sparisoma viride",
"Stegastes adustus",
"Stegastes diencaeus",
"Stegastes leucostictus",
"Stegastes partitus",
"Stegastes planifrons",
"Stegastes variabilis"
),
a = c(
0.0237,
0.004,
0.0415,
0.0174,
0.0239,
0.0153,
0.0124,
0.0155,
0.0147,
0.0335,
0.025,
0.0121,
0.0046,
0.0099,
0.0162,
0.0156,
0.025,
0.0349,
0.0349,
0.0349,
0.0349,
0.0349,
0.0349
),
b = c(
2.9752,
3.5328,
2.8346,
3.08,
3.0825,
3.0618,
3.1109,
3.0626,
3.0548,
2.7086,
2.9214,
3.0275,
3.4291,
3.1708,
3.0252,
3.0641,
2.9214,
2.9109,
2.9109,
2.9109,
2.9109,
2.9109,
2.9109
)
)The biomass calculation follows the methods of Williams and Polunin (2001) and Marks and Klomp (2003). For each herbivore species, the code raises the median fork length of the size bin to the power b, multiplies by a, and multiplies by the observed count. The coefficients a and b come from linear regression of log-transformed length-weight data (Bohnsack and Harper, 1988).
The biomass formula is:
Biomass (X) = median^b * a * counts
What happens here: the loop applies this formula to every record in fishcounts and stores the result in a new biomass column.
Summarize herbivore abundance and biomass into herbsummary, first summing across size classes within each transect, then averaging across transects for each site and year.
# first sum biomass and counts across size classes
herbsummary <- fishcounts |>
filter(period == "Annual") |>
group_by(trophicgroup, sppname, commonname, year, site, transect) |>
summarise(biomass = sum(biomass),
counts = sum(counts))
#then average biomass and counts across years and sites
herbsummary <- herbsummary |>
group_by(trophicgroup, sppname, commonname, year, site) |>
summarise(
meanbiomass = mean(biomass),
sdbiomass = sd(biomass),
nbiomass = length(biomass),
meancounts = mean(counts),
sdcounts = sd(counts),
ncounts = length(counts)
)Add the site depth so facets can be ordered shallow to deep.
Figure 1 stacks mean parrotfish abundance by species at each TCRMP site, with facets ordered shallow to deep and a dashed line at the 2005 bleaching year.
ggplot(herbsummary,
aes(
x = year,
y = meancounts,
color = sppname,
fill = sppname,
group = sppname
)) +
facet_wrap(~ site) +
geom_area(position = 'stack', alpha = 0.5) +
# geom_point(position = 'stack') +
# geom_line(position = 'stack') +
ylab("counts") +
scale_y_continuous(expand = c(0, 0)) +
geom_vline(
xintercept = 2005,
color = "gray50",
alpha = 0.5,
lty = "dashed"
) +
# theme_classic() +
theme(strip.text = element_text(size = 10)) +
theme(legend.position = "bottom") +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank())
Figure 2 shows the same site-faceted series for biomass, so heavier-bodied species contribute more than they do to the abundance view.
ggplot(herbsummary,
aes(
x = year,
y = meanbiomass,
color = sppname,
fill = sppname,
group = sppname
)) +
facet_wrap(~ site) +
geom_area(position = 'stack', alpha = 0.5) +
# geom_point(position = 'stack') +
# geom_line(position = 'stack') +
ylab("biomass") +
scale_y_continuous(expand = c(0, 0)) +
geom_vline(
xintercept = 2005,
color = "gray50",
alpha = 0.5,
lty = "dashed"
) +
# theme_classic() +
theme(strip.text = element_text(size = 10)) +
theme(legend.position = "bottom") +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank())
What happens here: the next chunks define the bite-rate and bite-size functions from Mumby (2006), apply them to every record, and combine them with counts and transect area to derive the proportion of reef grazed per hour.
The biterate function returns the bites per hour for one record, from the genus and species parameters.
biterate <- function(x) {
ind <- fishcounts[x, ]
if (ind$counts > 0) {
gi <-
genusinfo[which(genusinfo$genus == strsplit(ind$sppname, " ")[[1]][1]), ]
si <- speciesinfo[which(speciesinfo$species == ind$sppname), ]
fi <- forklengths[which(forklengths$sizebin == ind$sizebin), ]
r <-
gi$w * (gi$biterateparama - (gi$biterateparamb * fi$median) - si$offset)
} else {
r = 0
}
return(r)
}The bitesize function returns the area of one bite for the record, scaled by fork length.
Populate the bite-rate and bite-size columns for every record (eq. 2-3 in Mumby (2006)).
Multiply counts, bite rate, and bite size for each species and size class to get the area grazed by that group on the transect (part of eq. 4 in Mumby (2006)).
fishcounts$prod <-
fishcounts$counts * fishcounts$biterate * fishcounts$bitesizeSum the area grazed across species and size classes for each transect and year (rest of eq. 4 in Mumby (2006)).
Set the surveyed transect area: 60 m² before 2009 and 100 m² from 2009 onward.
fishcounts_peryear_transect$ta <- 100
fishcounts_peryear_transect$ta[which(fishcounts_peryear_transect$year < 2009)] <-
60Calculate g, the percentage of the reef area grazed per hour, for each transect and year (eq. 5 in Mumby (2006)).
fishcounts_peryear_transect$g <-
(fishcounts_peryear_transect$tg / 10000) * (1 / fishcounts_peryear_transect$ta) *
100Average g across transects to get an annual mean per site.
Average g across years to get one site-level mean.
Add the site depth to each grazing table so the figures can be ordered shallow to deep.
fishcounts_peryear_transect <-
merge(fishcounts_peryear_transect, sitedat, by = "site")
fishcounts_peryear_transect <-
fishcounts_peryear_transect[order(fishcounts_peryear_transect$depth),]
fishcounts_peryear_transect$site <-
factor(fishcounts_peryear_transect$site,
levels = unique(fishcounts_peryear_transect$site))
fishcounts_peryear <-
merge(fishcounts_peryear, sitedat, by = "site")
fishcounts_peryear <-
fishcounts_peryear[order(fishcounts_peryear$depth),]
fishcounts_peryear$site <- factor(fishcounts_peryear$site,
levels = unique(fishcounts_peryear$site))
fishcounts_siteave <-
merge(fishcounts_siteave, sitedat, by = "site")
fishcounts_siteave <-
fishcounts_siteave[order(fishcounts_siteave$depth),]
fishcounts_siteave$site <- factor(fishcounts_siteave$site,
levels = unique(fishcounts_siteave$site))Figure 3 compares grazing intensity across sites, ordered by depth, with the site mean and standard deviation over each year’s transect values. The comparable capstone figure used a 2007-2017 window, so the year range here differs.
boxp <- ggplot() +
geom_jitter(
data = fishcounts_peryear_transect,
aes(x = site, y = g, color =
year),
size = 1.4,
width = 0.2
) +
scale_color_continuous(low = "magenta", high = "lightblue") +
# geom_boxplot(data = fishcounts_peryear_transect, aes(x = site, y = g), alpha =
# 0.2, linewidth=1.5) +
scale_x_discrete(limits = rev) +
geom_point(data = fishcounts_siteave,
aes(x = site, y = gmean),
col =
"black",
size = 2) +
geom_errorbar(
data = fishcounts_siteave,
aes(
x = site,
ymax = gmean + gsd,
ymin = gmean - gsd
),
col = "black",
linewidth = 1
) +
ylab("grazing intensity (prop. reef grazed/h)") +
theme_bw() +
coord_flip(ylim = c(0, 1)) +
scale_y_continuous(expand = c(0, 0)) +
theme(legend.position = "bottom")
boxp
Figure 4 tracks the annual mean grazing intensity at each site over time, with facets ordered shallow to deep.
p <-
ggplot(
fishcounts_peryear,
aes(
x = year,
y = gmean,
fill = depth,
ymin = gmean - gsd,
ymax = gmean + gsd
)
) +
scale_fill_continuous(low = "red", high = "blue") +
facet_wrap(. ~ site , scales = "fixed") +
geom_ribbon(alpha = .5) +
geom_line(size = 0.25) +
geom_point(size = 0.5) +
ylab("grazing intensity (prop. reef grazed/h)") +
coord_cartesian(ylim = c(0, 1)) +
# scale_y_continuous(expand = c(0, 0)) +
geom_vline(
xintercept = 2005,
color = "gray50",
alpha = 0.5,
lty = "dashed"
) +
theme_bw() +
theme(strip.text = element_text(size = 10)) +
theme(legend.position = "bottom") +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank())
p
herbivoreCountsBiomassGrazingintensity (the per-transect product this page writes).This page writes one data product, the per-transect herbivore counts, biomass, and grazing intensities, with its metadata sidecar.
version 1.0.0 • in-review • data ≤ 2023-12-31