In this report, I use plots to visualize data from the US Fish and Wildlife Service Interagency Ecological Program’s Delta Boat Electrofishing Program.

This data was collected from 2018 through 2026 in the Sacramento-San Joaquin Delta. The raw data includes all fish captured (one/count per row), survey information, and environmental data from each site.

I have already filtered the data to include only rows with black bass (largemouth, smallmouth, spotted, and redeye bass) and columns that I am interested in analyzing.

Ask Questions About the Data

I have a hypothesis that more black bass are captured during warmer months when they are more active.

I also want to know if there if there any months where the fish captured are larger than average.

To do this, I will visualize the catch per unit effort (CPUE) by month, plot fish size by month, and then look at the relationship between CPUE and temperature.

First, I need to group the data from individual rows for each fish into a summary of catch by site and then calculate the CPUE of black bass at each site sampled and summarize the CPUE for each month and year.

Load Packages

library(tidyverse)
library(here)
library(ggbeeswarm)
library(RColorBrewer)
library(gganimate)
library(gifski)

Read and Prepare Data

# load cleaned data and filtered, 
# raw data was downloaded from EDI website

clean_blackbass <- read.csv(here("data", "clean_blackbass.csv"))

Transform Data

# aggregate site data from data of rows for each fish 
cleansites <- clean_blackbass %>% 
  group_by(latitude_start, sample_date, day, month, year,
           longitude_start, latitude_end, longitude_end,
           do_channel, temp_channel, turbidity_ntu, spec_conductance,
           sav, lwd, bank_type, channel_type, region_code, seconds_per_segment) %>% 
  filter(dead == "No") %>% 
  summarise(mean_fork= mean(fork_length),
            catch= sum(count), .groups = 'drop') 

I transformed the data from one row per fish to one row per site. If I need the length data from individual fish, I can use the original cleaned and filtered black bass data.

Calculate Catch Per Unit Effort (CPUE)

# calculate CPUE for each site in each month
cpue_site_month <- cleansites %>%
  filter(!is.na(month),
         !is.na(catch),
         !is.na(seconds_per_segment)) %>%
  group_by(month, latitude_start, longitude_start) %>%
  summarise(
    total_catch = sum(catch),
    total_seconds = sum(seconds_per_segment),
    cpue_site = total_catch / total_seconds,
    .groups = "drop"
  )

I removed any rows with missing data, then summarized the catch per month at each site. Then I caculated the total black bass catch per site each month and divided that by the effort to get CPUE.

Visualize the data

1. CPUE Per Month

Calculate summary statistics

# calculate monthly mean CPUE, SD, and SE across sites
cpue_month <- cpue_site_month %>%
  group_by(month) %>%
  summarise(
    n = sum(!is.na(cpue_site)),
    cpue_month = mean(cpue_site, na.rm = TRUE),
    sd = sd(cpue_site, na.rm = TRUE),
    se = sd / sqrt(n),
    .groups = "drop")

This information will be used to create error bars for my plot.

# factor month labels

cpue_month$month <- as.factor(cpue_month$month)

# plot monthly cpue

cpue_plot <- ggplot(cpue_month, aes(x = month, y = cpue_month)) +
  geom_point(size = 3) +
  geom_errorbar(
    aes(ymin = cpue_month - se,
        ymax = cpue_month + se),
    width = 0.2) +
  labs(
    x = "Month",
    y = "Mean CPUE (fish/s)",
    title = "Monthly Mean CPUE with Standard Error") +
  theme_classic()  

# Export plot as .png
ggsave("cpue_plot.png")

Figure 1. Mean monthly CPUE of black bass. Total catch per site by month divided by electrofishing effort in seconds per site by month. Error bars represent the standard deviations in CPUE each month across sites.

It looks like my hypothesis was incorrect. Overall, more more fish are caught in the fall and winter months than in the spring and summer.

2. Fish size by month

I want to plot the size of captured black bass by month to see if there are any patterns.

# factor months to create labels for plot
clean_blackbass$month <- as.factor(clean_blackbass$month)

# create plot
clean_blackbass %>% 
  filter(fork_length > 0) %>% 
  mutate(meanFL = mean(fork_length)) %>% 
  ggplot(aes(x = month, y = fork_length, 
             color = bigger)) + 
  geom_quasirandom(size = .5) + 
  geom_smooth(aes(group = 1), 
              method = "lm", 
              color = "black",
              linewidth = .5) + 
  stat_summary(aes(group = 1), 
               fun = mean, 
               geom = "point",
               shape = 21,
               fill = "gray",
               color = "black",
               size = 3) +
  scale_color_brewer(palette = "Set2", 
                     name = "Bigger Than Average") + 
  labs(
    x = "Month",
    y = "Fork Length (mm)",
    title = "Fork Length by Month",
    subtitle = "Distribution of fish sizes and mean for each month") +
  theme_classic()  
Figure 2. Monthly distribution of black bass fork lengths (mm). Individual points represent fish, with colors indicating whether fork length was greater than or less than the overall mean fork length. Gray points indicate the mean fork length for each month, and the black line represents the fitted linear trend across months. Only fish with fork lengths greater than zero were included.

Figure 2. Monthly distribution of black bass fork lengths (mm). Individual points represent fish, with colors indicating whether fork length was greater than or less than the overall mean fork length. Gray points indicate the mean fork length for each month, and the black line represents the fitted linear trend across months. Only fish with fork lengths greater than zero were included.

On average, fish size is fairly consistent throughout the year. However, looking at months individually, size slightly increases from January through July and is lower from August through December.

I suspect that this pattern is related to spawning, with reproductive-sized (>180mm) bass becoming more active as temperatures increase above 14C (Moyle 2002).

3. Temperature, CPUE, and Fish Size

Now, I will combine several variables to see how they relate to each other over time

# Plot CPUE by site and water temperature, with size showing mean fork length and color showing CPUE
graph1 <- cpue_site_month %>% 
  ggplot(aes(x = longitude_start, 
             y = temp_channel, 
             size = mean_fork, 
             color = CPUE)) +
  geom_point(alpha = 0.7) +
  scale_color_continuous(palette = "spectral", name = "CPUE") +
  labs(
    title = "Black Bass CPUE as a Function of Temperature",
    x = "Site Longitude",
    y = "Mean Channel Temperature (°C)",
    size = "Mean Fork Length")

# animate the graph to show change in temperature and CPUE by month

graph1_animation = graph1 +
  transition_time(month) +
  labs(subtitle = "Month: {frame_time}") +
  ease_aes("linear")

Figure 3. Channel temperature at each site with points showing CPUE as color and mean fork length as size. CPUE calculated as total catch divided by total sampling time at each site.

It is interesting to look at the change in temperature over time, but difficult to detect patterns in CPUE and fish length.

It appears that temperature reaches 16C around April or May at most sites, coinciding with the increase in fish size seen in the previous plot.

References:

Interagency Ecological Program (IEP), J. Stagg, J. Speegle, A. Nanninga, E. Huber, S. Yun, J. Falcon, L. Mitchell, R. McKenzie, and D. Marcetti. 2026. Interagency Ecological Program: USFWS Delta Boat Electrofishing Survey, 2018-2026 ver 7. Environmental Data Initiative. https://doi.org/10.6073/pasta/1e762637531d8b20a4a3b25361b3ab1f (Accessed 2026-10-19).

Moyle, 2002, Inland Fishes of California, Revised and Expanded. Berkeley, CA: University of California Press.