Madagascar Report Card - Benthic Cover and Fish Biomass

Published 19 May 2025, Updated 21 Jul 2025
39
By James P.W. Robinson - Coral Reef Ecologist, Lancaster University, Iain R. Caldwell - Lead Data Analyst, MERMAID,
Benthic PIT
Fishbelt
R
Quarto
Scatterplot
Bar Plot

Context

This example is based on a "Madagascar Report Card", originally created by James Robinson, to compare benthic cover and fish biomass among sites in Madagascar and attempt to identify whether there are patterns between benthic cover and fish biomass among those sites. Visualizing fish and benthic cover data together like this can be helpful for identifying if fish biomass and benthic cover are related among the sites and whether some sites are doing much better than others. This could be a first step in then examining possible reasons for those differences.

Getting and reshaping data

In this article we focus on the code for the visualizations but the full resource (see the bottom of this page for link) also includes how to get the data using the “mermaidr” package and some necessary reshaping of the data. If you want to fully recreate the visualization code below you should refer to that full resource.

Benthic cover by site and category

Stacked horizontal barplot

One of the first visualizations in the report card is a horizontal stacked bar plot showing the % cover of all high level benthic categories for each site as stacked horizontal bars. Visualizing the data in this way makes it easier to see differences across sites. Flipping the bars so they are horizontal rather than vertical is advantageous as you can include variable numbers of sites without changing the width of the plot. Sites are also ordered from highest to lowest % hard coral, with the values for % hard coral shown on the left, so that it is particularly easy to compare that benthic category across sites.

### Reshape the data so that hard coral categories are in rows
benthicTBL <- madagascarProjSampleEventsTBL %>%
  select(site, `percent_cover_Hard coral`:`percent_cover_Bare substrate`,
         TotalBiomass, percent_cover_Other) %>% 
  pivot_longer(-c(site, TotalBiomass),
               names_prefix = "percent_cover_",
               names_to = 'benthic',
               values_to = 'cover') %>%
  mutate(cover = ifelse(is.na(cover), 0, cover),
         #Make site into a factor with levels that sort by hard coral cover
         site = factor(site, levels = madagascarProjSampleEventsTBL$site))

benthicTBL <- benthicTBL %>% 
  mutate(benthic = factor(benthic, levels = unique(benthicTBL$benthic)))

### Assign colors to the benthic types to match the dashboard
benthic_colors <- c("Hard coral" = "#498fc9",
                    "Soft coral" = "#9ce5fa",
                    "Macroalgae" = "#b2b000",
                    "Turf algae" = "#d9eea8",
                    "Crustose coralline algae" = "#fbd7d5",
                    "Rubble" = "#f5f6af",
                    "Sand" = "#c1b180",
                    "Seagrass" = "#4d4d4d",
                    "Cyanobacteria" = "#870e00",
                    "Other invertebrates" = "#4e4e4e",
                    "Bare substrate" = "#f2f3f3")
  
benthicPlot <- ggplot(data = benthicTBL,
                      aes(x = as.numeric(site), y = cover, fill = benthic)) +
  geom_bar(position = 'stack', stat = 'identity', alpha = 0.9,
           color = "black", linewidth = 0.25) +
  labs(x = '', y = 'Cover (%)', fill = 'Benthic type',
       title = 'Benthos') +
  coord_flip() +
  scale_fill_manual(values = benthic_colors) +
  scale_y_reverse(expand = c(0,0)) + 
  scale_x_continuous(expand = c(0,0),
                     breaks = 1:length(unique(benthicTBL$site)),
                     labels = paste0(
                       round(benthicTBL$cover[benthicTBL$benthic == "Hard coral"]),
                       '%'),
                     sec.axis = sec_axis(~.,
                                         breaks = 1:length(unique(benthicTBL$site)),
                                         labels = unique(benthicTBL$site))) +
  theme_classic() +
  theme(legend.position = 'left',
        axis.text = element_text(size = 11, colour = 'black'),
        axis.line = element_line(colour = 'black'),
        axis.ticks = element_line(colour = 'black'),
        axis.title = element_text(size = 12, colour = 'black'),
        plot.subtitle = element_text(colour = 'black', size = 11, hjust = 0.5),
        legend.background = element_rect(fill = 'white', color = NA),
        legend.box.background = element_blank(),
        legend.key = element_rect(color = "black", linewidth = 0.25),
        legend.title = element_text(colour = 'black', face = 'bold'),
        plot.title = element_text(colour = 'black', size = 14, hjust = 0.5,
                                  face = 'bold'),
        plot.margin = unit(c(0.2, 0.1, 0, 0), 'cm'),
        axis.text.y.left = element_text(size = 9, color = '#498FC9'),
        axis.text.y.right = element_text(size = 9, color = 'black', hjust = 0.5),
        axis.line.y = element_blank(),
        axis.ticks.x = element_line(color = 'black'),
        axis.ticks.y = element_blank(),
        panel.border = element_blank())

benthicPlot

Fish biomass by site and trophic group

Stacked horizontal barplot

Fish biomass can also be visualized in a similar way. In this second visualization from the report card, the length of the bar represents the total biomass and each color within the bar represents a trophic group. The code is quite similar but we thought it would be useful to show an example focusing on fish biomass as well. 

### Reshape the data so that trophic groups are in rows
fishTBL <- madagascarProjSampleEventsTBL %>%
  select(site, management_rules, `percent_cover_Hard coral`,
         biomass_planktivore:TotalBiomass) %>% 
  pivot_longer(-c(site, management_rules,
                  `percent_cover_Hard coral`, TotalBiomass),
               names_prefix = "biomass_",
               names_to = 'TG',
               values_to = 'biomass') %>%
  mutate(biomass = ifelse(is.na(biomass), 0, biomass),
         #Make site into a factor with levels that sort by hard coral cover
         site = factor(site, levels = madagascarProjSampleEventsTBL$site),
         TG = case_when(TG == "planktivore" ~ "Planktivore",
                        TG == "herbivore-macroalgae" ~ "Herbivore (macroalgae)",
                        TG == "herbivore-detritivore" ~ "Herbivore (detritivore)",
                        TG == "invertivore-sessile" ~ "Invertivore (sessile)",
                        TG == "invertivore-mobile" ~ "Invertivore (mobile)",
                        TG == "omnivore" ~ "Omnivore",
                        TG == "piscivore" ~ "Piscivore",
                        TG == "other" ~ "Other",
                        .default = TG)) %>% 
  mutate(TG = factor(TG, levels = c("Planktivore",
                                    "Herbivore (macroalgae)",
                                    "Herbivore (detritivore)",
                                    "Invertivore (sessile)",
                                    "Invertivore (mobile)",
                                    "Omnivore",
                                    "Piscivore",
                                    "Other")))

### Assign colors to the trophic groups to match the dashboard
tg_colors <- c("Planktivore" = "#bebad8",
               "Herbivore (macroalgae)" = "#659034",
               "Herbivore (detritivore)" = "#ddee96",
               "Invertivore (sessile)" = "#f1da54",
               "Invertivore (mobile)" = "#e7b16d",
               "Omnivore" = "#9ccbc1",
               "Piscivore" = "#597db4",
               "Other" = "grey")

#### Recreate the stacked barplot with fish biomass by trophic group
fishBiomassPlot <- ggplot(data = fishTBL,
                          aes(x = site, y = biomass, fill = TG)) +
  geom_bar(position = 'stack', stat = 'identity', alpha = 0.9,
           color = "black", linewidth = 0.25) +
  labs(x = '', y = 'Biomass (kg/ha)', fill = 'Trophic group',
       title = 'Fish') +
  coord_flip() +
  scale_fill_manual(values = tg_colors) +
  geom_text(data = fishTBL,
            aes(x = site, y = TotalBiomass + 50,
                label = paste0(round(TotalBiomass, 0))),
            size = 2, hjust = 0, vjust = -.25) +
  geom_text(data = fishTBL,
            aes(x = site, y = TotalBiomass + 50,
                label = management_rules),
            size = 2, hjust = 0, vjust = 1.2) +
  scale_y_continuous(expand = c(0, 0),
                     limits = c(0, max(fishTBL$TotalBiomass) + 600)) +
  theme_classic() +
  theme(axis.text = element_text(size = 11, colour = 'black'),
        axis.line = element_line(colour = 'black'),
        axis.ticks = element_line(colour = 'black'),
        axis.title = element_text(size = 12, colour = 'black'),
        plot.subtitle = element_text(colour = 'black', size = 11, hjust = 0.5),
        legend.background = element_rect(fill = 'white', color = NA),
        legend.position = "right",
        plot.title = element_text(colour = 'black', size = 14,
                                hjust = 0.5, face = 'bold'),
        legend.box.background = element_blank(),
        legend.key = element_rect(color = "black", linewidth = 0.25),
        legend.title = element_text(colour = 'black', face = 'bold'),
        plot.margin = unit(c(0.2, 1, 0, 0.2), 'cm'),
        axis.ticks.y = element_blank(),
        axis.line.x = element_line(color = 'black'),
        axis.line.y = element_blank(),
        axis.ticks.x = element_line(color = 'black'),
        axis.text.y = element_text(hjust = 0.5, size = 8),
        panel.border = element_blank())

fishBiomassPlot

Comparing fish biomass and benthic cover

Scatter plot with fitted GAM 

There are also visualizations in the report card that combine fish biomass and benthic cover. The example below shows how fish biomass varies with % cover of different benthic categories using faceted scatterplots. Within each scatterplot, the data is fitted to a generalized additive model (GAM) using the geom_smooth function. You will notice that there is no fitted line in the facet for crustose coralline algae (CCA) - this is because there are too many zeroes for % cover (i.e. a lot of the surveys had no CCA). Although we do not show it here, there is another visualization combining fish biomass and benthic cover In the full resource (link below) showing how fish biomass of different trophic groups varies with % hard coral cover.

biomassVsCoverScatterplots <-
  ggplot(benthicTBL %>% 
           filter(!benthic %in% c("Cyanobacteria",
                                  "Other invertebrates",
                                  "Seagrass",
                                  "Other")),
         aes(cover, TotalBiomass)) +
  geom_smooth(method = 'gam', col = 'grey', fill = 'grey90') +
  labs(x = 'Benthic cover (%)',
       y = "Fish biomass (kg/ha)",
       title = '') +
  geom_point(aes(fill = benthic), shape = 21, alpha = 0.8, size = 2) +
  scale_color_manual(values = benthic_colors) +
  facet_wrap(~benthic, nrow = 4, scales = 'free') +
  theme_classic() +
  theme(axis.text = element_text(size = 9, colour = 'black'),
        axis.line = element_line(colour = 'black'),
        axis.ticks = element_line(colour = 'black'),
        axis.title = element_text(size = 12, colour = 'black'),
        plot.subtitle = element_text(colour = 'black', size = 11, hjust = 0.5),
        legend.background = element_rect(fill = 'white', color = NA),
        legend.box.background = element_blank(),
        legend.title = element_blank(),
        legend.position = 'none',
        strip.text = element_text(hjust = 0, face = "bold"),
        strip.background = element_blank(),
        plot.margin = unit(c(0, .5, 0, .5), 'cm'),
        panel.border = element_blank())

biomassVsCoverScatterplots