How does kelp density affect the fish communities in the Northern Channel Islands

Author
Affiliation

Introduction

The Channel Islands off the coast of California are some of the most diverse areas of temperate marine ecosystems around the world. These rocky reefs host a plethora of fish, invertebrate, and algae species. The primary producer and foundation of the habitat is Giant Kelp, Macrocystis pyriferia, which provide food and shelter to many species. Kelp forest faces threats of increasing ocean temperatures, introductions of invasive species, and loss of keystone species through anthropogenic extraction. This infographic aims to explore the relationship between kelp density and fish species diversity and abundance using fish survey data collected by the Caselle lab of University California, Santa Barbara. I aim to identify patterns between fish diversity and abundance with kelp density through the water column.

Species Richness

To identify any patterns between species richness and kelp density, I created a bar chart that quantifies the total number of unique species observed at each kelp density bin across the three survey levels in the water column (canopy, midwater, and bottom). I chose a bar chart as my graphic form, because I wanted the comparison across kelp densities to be straight forward for the viewer. The survey levels were reordered to represent the water column, contextualizing the data better for the viewer. To account for the uneven sampling at the varying kelp densities, I calculated the average number of species by surveys at each level and kelp density. I selected the colors from a photo of kelp, using a dark blue-green as the background to contrast with the light green of the kelp. I intentionally increased the ink to data ratio to help create the feeling of being underwater. As the density of kelp increases across the y axis, the column increases in opacity. My overall goal of this figure was to make th viewer feel as if they are completing a fish survey.

Biodiversity

While patterns of species richness show the number of unique species seen at any point, I quantified biodiversity using the standard equation to look at the biodiversity through the water column. I created a heatmap representing the biodiversity index at all depth and kelp density combinations. A heatmap was ideal because I could represent three different variables in one figure without having too much information for the viewer to digest. This figure aimed to identify any patterns in biodiversity of the water column through the kelp densities. In order to contextualize the data better, I reversed the y-axis to represent depth in a more intuitive way. I also added a text label to explain why there were gaps in the visualization.

Common fishery targets

While species diversity is interesting to me, I wanted my infographic to reach a broader audience by including a fishing portion. I quantified the number of observations of the 10 most common targeted fish species using a diverging bar chart to represent fish seen in kelp vs. “no” kelp. No kelp was classified as less than 5% kelp coverage, because that was minimum density to complete a canopy survey during data collection. The x and y axis were flipped to make reading the species names easier and to make the observations in the kelp densities more intuitive. I also added silhouettes of the species in the final infographic using Affinity to help give a visual aid for viewers who may not recognize the species name. In the final infographic, I adjusted the order of species, grouping rockfish together and ordering from most observations to least for both the rockfish and other species.

Overall design choices

Theme Each plot was modified to create the easiest viewing. Axis were flipped or reversed when it was seemingly more intuitive. The plots all aimed to have an ocean aesthetic that was cohesive for my final infographic.

Text Text colors and sizes were adjusted to increase readability, but avoid making things feel cramped. Many text adjustments (e.g. removing redundant axis titles, adding units to labels) were done using Affinity for the final infographic.

Typography For this project, I selected the fonts “Spectral” and “Inconsolata” to keep my figures fun and nice to look at. I wanted the text to lure the reader in, without being too distracting or hard to read. Overall, these figures aim to show patterns, but should not be used to make any ecological conclusions, leading me to select a more relaxed, fun typefaces.

Colors To keep my colors consistent and true to a kelp forest, I used a photo (seen as the cover photo) of the kelp forest and selected contrasting colors to make the viewer feel as if they are in a kelp forest. The two main colors I incorporated into my figures were a dark blue-green and light yellow-green to represent the water and the kelp, as well as create a contrast between the two.

Centering the Primary Message I wanted every figure to have a primary message that could be understood through the title. While additional patterns can be recognized through viewing the data, I wanted the audience to see my big picture takeaway at first glance.

Accessibility To make my figures accessible, I used contrasting colors to keep things colorblind friendly. I also added alt-text describing my figures and the main takeaway for each.

Code
library(tidyverse)
library(here)
library(vegan)
library(ggtext)
library(sysfonts)



subtidal <- read_csv(here("posts", "eds240-infographic", "data", "ucsb_fish_clean_obs_1999-2024.csv"))
taxon <- read_csv(here("posts", "eds240-infographic", "data", "PISCO_kelpforest_taxon_table.1.3.csv")) %>% 
  filter(campus == "UCSB")

fish_taxon <- left_join(subtidal, taxon, by = "classcode") %>% 
  select(year, site, side, zone, level, transect, classcode, fish_tl, depth, pctcnpy, count, Family, Genus, Species, common_name) %>% 
  janitor::clean_names()

fish_role <- read_csv(here("posts", "eds240-infographic", "data", "ucsb_fish_clean_tx_bm_1999-2024.csv")) %>% 
  filter(year > 2005) %>% 
  select(classcode, BroadTrophic, Targeted) %>%
  distinct() %>%
  group_by(classcode) %>% 
  summarise(BroadTrophic = paste(BroadTrophic, collapse = ", "),
            Targeted = paste(Targeted, collapse = ", "),
    .groups = 'drop') %>% 
  janitor::clean_names()


fish <- left_join(fish_taxon, fish_role, by = "classcode") %>% 
  janitor::clean_names()

fish_clean <- fish %>% 
  filter(!is.na(fish_tl)) %>%
  group_by(site) %>% 
  mutate(total_years = length(unique(year))) %>% # Calculate number of years each site was surveyed
  ungroup() %>%
  mutate(island = case_when( # Assign sites to respective islands
    str_detect(pattern = "SRI", string = site) ~ "Santa Rosa", 
    str_detect(pattern = "SCI", string = site) ~ "Santa Cruz",
    str_detect(pattern = "SMI", string = site) ~ "San Miguel",
    str_detect(pattern = "ANA", string = site) ~ "Anacapa",
    TRUE ~ "remove"), 
    level = factor(level, levels = c("CAN", "MID", "BOT"))) %>% # Refactor survey levels
  filter(island != "remove", 
         total_years >= 15,
         year > 2005
         ) %>% # Select only sites with >=15 years of surveys
  select(-total_years)

# Calculate canopy averages at transect level
kelp <- fish_clean %>% 
  group_by(site, side, transect, year) %>% 
  summarise(av_pctcnpy = round(mean(pctcnpy, na.rm = TRUE), digits = 0)) %>% 
  ungroup() %>%
  mutate(cnpy_pct = case_when( # Bin percent canopy cover
    av_pctcnpy <= 10 ~ "0-10",
    av_pctcnpy <= 20 ~ "11-20",
    av_pctcnpy <= 30 ~ "21-30",
    av_pctcnpy <= 40 ~ "31-40",
    av_pctcnpy <= 50 ~ "41-50",
    av_pctcnpy <= 60 ~ "51-60",
    av_pctcnpy <= 70 ~ "61-70",
    av_pctcnpy <= 80 ~ "71-80",
    av_pctcnpy <= 90 ~ "81-90",
    av_pctcnpy > 90 ~ "91-100",
    TRUE ~ NA,
    )
  ) %>% 
  filter(!is.na(cnpy_pct))

fish_kelp <- left_join(fish_clean, kelp, by = join_by(site, side, transect, year)) # Attach bins to data

species_sums <- fish_kelp %>% 
  group_by(family) %>% 
  mutate(total_obs = n(),
         total_counts = sum(count)) %>% 
  ungroup() %>% 
  mutate(fish_family = case_when(
    total_counts >= 30000 ~ family,
    TRUE ~ "Other"))

# Color palettes
plot_palette = c("#001806", "#7F6F2B", "#9A7408", "#D4C569", "#AA9D52", "#EBF4F2", "#B6E5F0", "#96B8A9", "#1E6848", "#004B5D", "#002B2B")



# fonts
font_add_google(name = "Spectral", family = "spectral")
font_add_google(name = "Inconsolata", family = "inconsolata")

species_sums$fish_family <- factor(species_sums$fish_family, 
                                   levels = c("Atherinopsidae", "Embiotocidae", "Labridae", "Pomacentridae", "Sebastidae", "Serranidae", "Other"), 
                                   labels = c("Baitfish", "Surfperch", "Wrasses", "Damselfish", "Rockfish", "Basses", "Other"))

level_labs <- c(CAN = "Canopy", 
                MID = "Midwater", 
                BOT = "Bottom")

unique_sp <- species_sums %>% 
  group_by(year, cnpy_pct, level, side, zone, site) %>% 
  summarize(transects = max(transect),
            unique_species = length(unique(classcode))) %>% 
  ungroup() %>% 
  group_by(cnpy_pct, level) %>% 
  summarize(av_sp = round(mean(unique_species), 0))

ggplot(unique_sp) +
  geom_col(aes(cnpy_pct, av_sp, alpha = cnpy_pct), fill = "#D4C569", color = NA, show.legend = FALSE) +
  #geom_jitter(aes(cnpy_pct, unique_species, color = fish_family, size = total_fish), alpha = 0.9) +
  #theme(legend.position = "none") +
  scale_color_manual(values = fish) +
  #scale_alpha_discrete(range = c(0.2,1)) + 
  facet_wrap(~level, 
             #scales = "free", 
             ncol = 1, 
             labeller = labeller(level = level_labs)) +
  theme_light() +
  theme(plot.title = element_markdown(family = "inconsolata"),
        axis.title = element_markdown(family = "inconsolata", size = 12),
        axis.text = element_markdown(family = "spectral", size = 10),
        strip.text = element_markdown(family = "inconsolata", color = "#002B2B", size = 12),
        panel.background = element_rect(fill = "#002B2B"), 
        panel.grid.major = element_line(linewidth = 0),
        panel.grid.minor = element_line(linewidth = 0),
        legend.background = element_rect(fill = NA),
        legend.box.background = element_blank(),
        plot.background = element_rect(fill = "#EBF4F2"),
        strip.background = element_blank(),
        strip.placement = "inside",
        axis.title.x = element_text(margin = margin(t = 5, r = 0, b = 0, l = 0, unit = "pt")),) +
  labs(x = "Kelp Density (%)",
       y = "Number of observed species",
       #color = "Family",
       #size = "Total observed fish",
       title = "On average, number of unique species observed increases \nwith kelp density and depth")

A bar chart representing the average number of species across kelp density bins of 10%, faceted by area in the water column that the survey was completed. Overall, number of species increased with increasing kelp, and the bottom water surveys saw the most species on average.

Code
fish_kelp %>% 
  mutate(depth_ft = round(depth * 3.28084, digits = 0), # convert depth from meters to feet
    depth_zone = case_when(
      depth_ft <= 10 ~ "0-10",
      depth_ft <= 20 ~ "11-20",
      depth_ft <= 30 ~ "21-30",
      depth_ft <= 40 ~ "31-40",
      depth_ft <= 50 ~ "41-50",
      depth_ft > 50 ~ ">50",
      TRUE ~ NA
  )) %>% 
  filter(!is.na(depth_zone)) %>% 
  mutate(depth_zone = forcats::fct_relevel(depth_zone,
         ">50", "41-50", "31-40", "21-30", "11-20", "0-10")) %>% 
  group_by(cnpy_pct, depth_zone, classcode) %>% 
  summarise(num_sp = sum(count),
         n = num_sp*(num_sp-1)) %>% 
  ungroup() %>% 
  group_by(cnpy_pct, depth_zone,) %>% 
  summarize(total_indiv = sum(num_sp),
         N = total_indiv*(total_indiv-1),
         biodiv = N / sum(n)) %>% 
  filter(!is.infinite(biodiv)) %>%
      #av_species = mean(length(unique(classcode)), rm.na = TRUE)) %>% 
  ggplot(aes(cnpy_pct, depth_zone, fill = biodiv)) +
  geom_tile() +
  scale_fill_gradient(high = "#002B2B", low = "#D4C569") +
  labs(x = "Kelp Density (%)",
       y = "Depth zone (ft)",
       fill = "Biodiversity\nIndex",
       title = "Biodiversity generally increases with \ndepth and intermediate kelp density" ) +
  scale_x_discrete(expand = c(0, 0)) + # Remove dead space
  scale_y_discrete(expand = c(0, 0)) +
  annotate("text", x = 9.5, y = 1, label = "No surveys\ncompleted below\n50ft at >80% kelp", size =2.9, color = "#001806", family = "inconsolata") +
  theme_light() +
  theme(plot.title = element_markdown(family = "inconsolata"),
        axis.title = element_markdown(family = "inconsolata", size = 12),
        axis.text = element_markdown(family = "spectral", size = 10),
        panel.background = element_rect(fill = "#EBF4F2"),
        plot.background = element_rect(fill = "#EBF4F2"),
        legend.background = element_rect(fill = "#EBF4F2"), 
        #legend.title = element_markdown(family = "spectral"),
        #legend.text = element_markdown(family = "inconsolata"),
        panel.grid.minor = element_blank(),
        text = element_text(color = "#001806"),
        axis.title.x = element_text(margin = margin(t = 5, r = 0, b = 0, l = 0, unit = "pt")))

A heatmap representing the biodiversity index with depth zone from 0 to 50ft on the y-axis and kelp desnity bins of 10% from 0-100% on the x-axis. In general, biodiversity increased with depth and the highest biodiversity was found in the intermediate densities of kelp.

Code
target <- fish_kelp %>% 
  filter(targeted == "Targeted") %>% 
  group_by(classcode) %>% 
  summarize(total_obs = n(),
         total_counts = sum(count)) %>% 
  filter(total_obs >= 5) %>% 
  slice_max(total_obs, n = 10) %>% 
  ungroup()


fish_kelp %>% 
  filter(classcode %in% target$classcode) %>% 
  mutate(kelp_dens = case_when(
    av_pctcnpy < 5 ~ "less_kelp",
    TRUE ~ "more_kelp"
  )) %>% 
  group_by(common_name, kelp_dens) %>% 
  summarize(total_obs = n(),
            total_counts= as.numeric(sum(count, na.rm = TRUE))) %>% 
  ungroup() %>% 
  mutate(count_dif = case_when(
    kelp_dens == "less_kelp" ~ (total_obs * -1),
    TRUE ~ total_obs
  )) %>% 
  ggplot() +
  geom_col(aes(count_dif, common_name, fill = kelp_dens)) +
  scale_fill_manual(labels = c("No Kelp", "5% or more kelp coverage"), values = c("#002B2B", "#D4C569")) +
 scale_x_continuous(breaks = c(-5000,-2500,0,2500,5000,7500,10000,15000),
                   labels = c(breaks = c(5000,2500,0,2500,5000,7500,10000,15000))) +
  labs(fill = "",
       x = "Number of observations",
       y = "",
       title = "Most common targeted species are \nmore often seen in kelp") +
  theme_light() +
  theme(plot.title = element_markdown(family = "inconsolata"),
        axis.title = element_markdown(family = "inconsolata", size = 12),
        axis.text = element_markdown(family = "spectral", size = 10),
        legend.text = element_markdown(family = "spectral", size = 10),
        panel.background = element_rect(fill = "#EBF4F2"),
        plot.background = element_rect(fill = "#EBF4F2"),
        legend.background = element_rect(fill = "#EBF4F2"),
        legend.position = "top",
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        text = element_text(color = "#001806"), 
        axis.title.x = element_text(margin = margin(t = 5, r = 0, b = 0, l = 0, unit = "pt")),)

A diverging bar plot comparing observations of the 10 most common fishery target species in kelp densities above and below 5%, with fish species on the y-axis and observations on the x-axis. Kelp bass were the most observed species and was significantly observed in kelp densities above 5%. Additional species were on average more often seen in kelp densities above 5% with the exception og Ocean Whitefish and some rockfish having equal observations.