# 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_dataSalmonid Mortality Data
tidytuesday
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
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)) 