Salmonid Mortality Data

tidytuesday
Published

March 20, 2026

Link to data: TidyTuesday.

The Fish Health Report is the Norwegian Veterinary Institute’s annual status report on the health and welfare situation for Norwegian farmed fish and is based on official statistics, data from the Norwegian Veterinary Institute and private laboratories. The report also contains results from a survey among fish health personnel and inspectors from the Norwegian Food Safety Authority, as well as assessments of the situation, trends and risks.

0. Data

# Packages
library(tidyverse)

# Load data
tuesdata <- tidytuesdayR::tt_load('2026-03-17')

# Extract data
monthly_losses_data <- tuesdata$monthly_losses_data
monthly_mortality_data <- tuesdata$monthly_mortality_data

County-Level Salmon Losses

1. Visualization

Code
# Pre-process
monthly_losses_data %>% 
  filter(geo_group == 'county' & species == 'salmon') %>% 
  group_by(region, year = factor(year(date))) %>% 
  summarise(losses = sum(losses)) %>% 
  
# Plot
  ggplot(mapping = aes(y = reorder(region, losses, sum), 
                       x = losses/1e6, fill = year)) + 
  # Stacked bar chart
  geom_col(width = 1/2) + 
  
  # Black outline
  geom_col(aes(x = total_losses/1e6, y = reorder(region, total_losses), fill = NULL),
           data = monthly_losses_data %>% 
            filter(geo_group == 'county' & species == 'salmon') %>% 
             group_by(region) %>% 
            summarise(total_losses = sum(losses)),
           fill = NA, color = 'black', width = 1/2) + 
  
  # Text labels
  geom_text(aes(x = total_losses/1e6, y = reorder(region, total_losses), fill = NULL, label = round(total_losses/1e6, 2)),
            size = 2,
           data = monthly_losses_data %>% 
            filter(geo_group == 'county' & species == 'salmon') %>% 
             group_by(region) %>% 
            summarise(total_losses = sum(losses)),
           hjust = -0.2) + 
  
  # Aesthetics
  scale_fill_brewer(palette = 'Reds') +
  labs(x = 'Total losses\n(in millions)', fill = 'Year', y = '',
       title = 'Salmon Losses in Norwegian Counties',
       subtitle = '2020 - 2025',
       caption = 'Source: #TidyTuesday Week 13, 2026') + 
  scale_x_continuous(limits = c(0, 100)) + 
  theme_minimal() + 
  theme(aspect.ratio = 1/2, panel.grid.major.y = element_blank(),
        legend.title = element_text(hjust = 0.5, size = 10, face = 'bold'),
        legend.text = element_text(size = 7, hjust = 0),
        plot.title = element_text(face = 'bold', hjust = 0.5, size = 14),
        plot.subtitle = element_text(hjust = 0.5, size = 10),
        plot.caption = element_text(size = 8),
        axis.text.y = element_text(hjust = 1, size = 7))

Mortality Rates Over Time

1. Data Cleaning

Code
# Average median mortality
monthly_mortality_data_avg <- monthly_mortality_data %>% 
  filter(geo_group == 'county') %>% 
  group_by(date) %>% 
  summarize(mean_mortality = mean(median))

2. Visualization

Code
# Figure
monthly_mortality_data %>% 
  
  # Filter to county-level
  filter(geo_group == 'county') %>%
  
  # Create plot
  ggplot(mapping = aes(x = date, y = median, group = region)) + 
  
  # Median mortality per county
  geom_line(aes(linetype = 'County\nMedian'), 
            alpha = 0.2, color = '#97a6c4') + 
  
  # Mean of median mortality
  geom_line(data = monthly_mortality_data_avg, 
            mapping = aes(y = mean_mortality, group = NULL,
                          linetype = 'Average County\nMedian'),
            color = '#384860') + 
  
  # Aesthetics
  labs(y = 'Mortality Rate', title = 'Mortality Rate of Salmon',
       subtitle = "Norway (2020-2025)",
       x = '', linetype = '') + 
  scale_linetype_manual(
    values = c(
      "County\nMedian" = "solid",
      "Average County\nMedian" = "dashed"
    )
  ) +
  scale_x_date(date_breaks = 'year', date_labels = '%Y') +
  guides(
    linetype = guide_legend(
      label.position = "top"
    )
  ) +
  theme_bw() + 
  theme(aspect.ratio = 0.6, 
        plot.title = element_text(hjust = 0.5, face = 'bold', size = 14),
        plot.subtitle = element_text(hjust = 0.5, size = 10),
        strip.text = element_text(size = 8),
        legend.position = 'right',
        legend.text = element_text(size = 8),
        legend.spacing.y = unit(5, 'in'),
        panel.spacing = unit(1.5, "lines"),
        panel.grid.minor = element_blank(),
        panel.grid.major.x = element_blank(),
        axis.text = element_text(size = 10),
        axis.title = element_text(size = 12),
        axis.text.x = element_text(hjust = 0.5, size = 10))

Composition of Losses

1. Data Cleaning

Dince

Code
# Custom colors
my_cols <- c('dead'='#5e4c5f',
             'discarded'='#ffbb6f',
             'other'='#999999')

# Combine 'other' and 'escaped'
bg_df <- monthly_losses_data %>% 
  filter(geo_group == 'country' & species == 'salmon') %>% 
  mutate(other = other + escaped, .keep = 'unused') %>% 
  pivot_longer(dead:other, values_to = 'count', names_to = 'type_of_loss') %>% 
  group_by(year = year(date), type_of_loss) %>% 
  summarise(count = sum(count)/1e6)

2. Visualization

Code
bg_df %>% 
  
  # Base chart
  ggplot(mapping = aes(x = year, y = count, color = type_of_loss)) + 
  
  # Annotations behind chart features
  annotate('rect', xmin = 2023, xmax = 2025,
           ymin = -Inf, ymax = Inf, 
           fill = 'grey', alpha = 0.2) +
  annotate('text',
           x = 2024, y = 32, label = 'Shift in composition?',
           size = 2.5) + 
  
  # Chart
  geom_line(linewidth = 1) + 
  geom_point() + 
  
  # Aesthetics
  scale_x_continuous(breaks = 2020:2025) +
  scale_color_manual(values = my_cols) + 
  labs(x = 'Year', y = 'Count\n(in millions)', 
       title = 'Composition of Farmed Salmon Loss in Norway',
       subtitle = 'The majority of salmon loss is attributed to death, which peaked in 2023.\nIn the following years, salmon death decreased while other types of loss\nincreased.',
       color = 'Type of Loss') + 
  theme_bw() + 
  theme(plot.title = element_text(size = 12, face = 'bold', hjust = 0.0),
        plot.subtitle = element_text(size = 8, color = 'grey30'),
        axis.text = element_text(size = 8),
        axis.title = element_text(size = 10),
        strip.text = element_text(size = 8),
        panel.grid = element_blank(),
        aspect.ratio = 1/2,
        legend.position = 'bottom',
        legend.title = element_text(size = 10),
        legend.text = element_text(size = 8))