This report runs quality-control checks on the commercial fishery (catch) data in the per-member files (Chile.csv, China.csv, … Russia.csv). # 1. Load and prepare data

Member–fleet allowlist

Each member fishes a specific fleet (or two for Chile). The standard ALK template includes empty sheets for all fleets, so we filter to keep only the (member, fleet) combinations that actually correspond to each member’s fishery:

member_fleet <- tribble(
  ~member,    ~fleet_canon,
  "Chile",    "F1",     # Fleet 1: Northern Chile (inside EEZ)
  "Chile",    "F2",     # Fleet 2: South-Central Chile (EEZ/HS)
  "Peru",     "F3",     # Fleet 3: Peru & Ecuador (inside EEZ)
  "Ecuador",  "F3",     # Fleet 3 (Ecuador, same fleet as Peru)
  "EU",       "F4",     # Fleet 4: Offshore (outside EEZ)
  "Korea",    "F4",     # Fleet 4
  "China",    "F4",     # Fleet 4
  "Russia",   "F4"      # Fleet 4
)

member_fleet %>% kable(caption = "Allowed (member, fleet) combinations")
Allowed (member, fleet) combinations
member fleet_canon
Chile F1
Chile F2
Peru F3
Ecuador F3
EU F4
Korea F4
China F4
Russia F4

Load CSVs

data_dir <- params$data_dir

read_member <- function(m) {
  fp <- file.path(data_dir, paste0(m, ".csv"))
  if (!file.exists(fp)) {
    warning("Missing: ", fp); return(NULL)
  }
  read_csv(fp, show_col_types = FALSE,
           col_types = cols(
             submission_year = col_integer(),
             data_year = col_integer(),
             member = col_character(),
             fleet = col_character(),
             variable = col_character(),
             quarter = col_character(),
             length_cm = col_double(),
             age = col_integer(),
             metric_detail = col_character(),
             value = col_double(),
             unit = col_character(),
             version = col_character(),
             data_source = col_character(),
             source_file = col_character()
           ))
}

raw <- bind_rows(lapply(params$members, read_member))
cat("Total rows loaded:", format(nrow(raw), big.mark=","), "\n")
## Total rows loaded: 136,751

Normalize fleet labels

Some submissions use full descriptive names (“Fleet 1: Northern Chile (inside EEZ)”), others just “Fleet 1” or “Fleet1”. Collapse to a canonical short label F1/F2/F3/F4:

canon_fleet <- function(x) {
  x[is.na(x)] <- ""
  x_low <- tolower(x)
  x_clean <- str_replace_all(x_low, "[^a-z0-9 ]", " ")
  case_when(
    str_detect(x_clean, "fleet ?1|northern chile") ~ "F1",
    str_detect(x_clean, "fleet ?2|south.?central|eez/hs") ~ "F2",
    str_detect(x_clean, "fleet ?3|peru") ~ "F3",
    str_detect(x_clean, "fleet ?4|offshore") ~ "F4",
    x == "" ~ NA_character_,
    TRUE ~ x
  )
}

raw <- raw %>% mutate(fleet_canon = canon_fleet(fleet))

For the legacy data sources couldn’t infer a fleet, fall back to the member’s expected fleet (only valid because each member except Chile has exactly one fleet — Chile’s NA-fleet rows would be ambiguous and are dropped):

raw <- raw %>%
  left_join(
    member_fleet %>% group_by(member) %>%
      summarise(default_fleet = ifelse(n() == 1, fleet_canon, NA_character_),
                .groups = "drop"),
    by = "member"
  ) %>%
  mutate(fleet_canon = coalesce(fleet_canon, default_fleet)) %>%
  select(-default_fleet)

Apply filters: version, member-fleet allowlist, zeros, latest submission

n_raw <- nrow(raw)

# 1) version filter
df <- raw %>% filter(!str_detect(version, params$exclude_versions))
cat(sprintf("After version filter: %s rows (%.1f%% of raw)\n",
            format(nrow(df), big.mark=","), 100 * nrow(df) / n_raw))
## After version filter: 128,188 rows (93.7% of raw)
# 2) member-fleet allowlist - drop rows from fleets that the member doesn't fish
n_before_allowlist <- nrow(df)
df <- df %>% inner_join(member_fleet, by = c("member","fleet_canon"))
cat(sprintf("After member-fleet allowlist: %s rows (dropped %s)\n",
            format(nrow(df), big.mark=","),
            format(n_before_allowlist - nrow(df), big.mark=",")))
## After member-fleet allowlist: 107,875 rows (dropped 20,313)
# 3) drop zero-valued rows (zero placeholders are noise, not data)
n_before_zero <- nrow(df)
df <- df %>% filter(!is.na(value), value != 0)
cat(sprintf("After dropping zero/NA values: %s rows (dropped %s)\n",
            format(nrow(df), big.mark=","),
            format(n_before_zero - nrow(df), big.mark=",")))
## After dropping zero/NA values: 77,935 rows (dropped 29,940)
# 4) dedupe by latest submission per identical
#    (member, fleet, year, quarter, length, age, variable, metric_detail, data_source)
if (isTRUE(params$pick_latest_submission)) {
  n_before_dedup <- nrow(df)
  df <- df %>%
    group_by(data_year, member, fleet_canon, variable, quarter,
             length_cm, age, metric_detail, data_source) %>%
    slice_max(submission_year, n = 1, with_ties = TRUE) %>%
    ungroup()
  cat(sprintf("After picking latest submission: %s rows (dropped %s)\n",
              format(nrow(df), big.mark=","),
              format(n_before_dedup - nrow(df), big.mark=",")))
}
## After picking latest submission: 69,557 rows (dropped 8,378)
# Member × fleet labels for plotting
df <- df %>%
  mutate(mf  = paste(member, fleet_canon, sep = " "),
         qtr = factor(quarter, levels = c("Q1","Q2","Q3","Q4","all")))

# Stable factor ordering for mf so plots come out in the same member order
mf_levels <- df %>% distinct(member, fleet_canon, mf) %>%
  arrange(member, fleet_canon) %>% pull(mf)
df <- df %>% mutate(mf = factor(mf, levels = mf_levels))

Coverage table

df %>%
  count(member, fleet_canon, variable) %>%
  pivot_wider(names_from = variable, values_from = n, values_fill = 0) %>%
  arrange(member, fleet_canon) %>%
  kable(caption = "Row count per (member × fleet) × variable (after all filters)") %>%
  kable_styling(bootstrap_options = c("striped","hover"), font_size = 11)
Row count per (member × fleet) × variable (after all filters)
member fleet_canon age_length_key catch catch_at_age catch_at_age_proportion length_freq mean_length_at_age mean_weight_at_age numbers_at_age numbers_at_age_proportion sampling weight_at_length age_freq_at_length age_proportion_at_length
Chile F1 2373 64 1096 1096 1453 1559 1559 1559 1096 189 3500 0 0
Chile F2 4921 67 1955 1955 2089 2576 2576 2576 1955 224 3800 0 0
China F4 54 55 0 0 787 18 18 18 0 54 77 0 0
EU F4 1722 49 0 0 1752 231 230 221 0 112 377 5884 5883
Ecuador F3 0 13 0 0 14 0 0 0 0 0 0 0 0
Korea F4 0 33 0 0 617 0 0 0 0 52 237 0 0
Peru F3 4560 59 0 0 1688 324 324 325 0 129 238 0 0
Russia F4 1421 36 0 0 691 253 255 255 0 104 149 0 0

2. Catch

Catch is reported by quarter (Q1–Q4). The “all”-quarter aggregate row is excluded to avoid double-counting.

catch <- df %>%
  filter(variable == "catch",
         metric_detail == "official_landings",
         quarter %in% c("Q1","Q2","Q3","Q4")) %>%
  group_by(member, fleet_canon, mf, data_year, quarter, qtr) %>%
  summarise(catch_t = sum(value, na.rm = TRUE), .groups = "drop")

catch_annual <- catch %>%
  group_by(member, fleet_canon, mf, data_year) %>%
  summarise(catch_t = sum(catch_t, na.rm = TRUE), .groups = "drop")

Annual totals

ggplot(catch_annual, aes(data_year, catch_t / 1000, fill = mf)) +
  geom_col() +
  facet_wrap(~ mf, scales = "free_y", ncol = 2) +
  scale_x_continuous(breaks = pretty_breaks(6)) +
  scale_y_continuous(labels = comma) +
  guides(fill = "none") +
  labs(title = "Annual catch (official landings)",
       x = NULL, y = "Landings (thousand tonnes)")

Year × quarter heatmap

ggplot(catch, aes(data_year, qtr, fill = catch_t / 1000)) +
  geom_tile() +
  facet_wrap(~ mf, ncol = 2) +
  scale_fill_viridis_c(option = "C", labels = comma, name = "kt") +
  scale_x_continuous(breaks = pretty_breaks(6)) +
  labs(title = "Catch (thousand tonnes) by year × quarter",
       subtitle = "Empty cells = no catch reported that quarter",
       x = NULL, y = "Quarter")

Quarter share within year

share <- catch %>%
  group_by(member, fleet_canon, mf, data_year) %>%
  mutate(share = catch_t / sum(catch_t, na.rm = TRUE)) %>%
  ungroup() %>%
  filter(is.finite(share))

ggplot(share, aes(data_year, share, fill = quarter)) +
  geom_area(position = "stack") +
  facet_wrap(~ mf, ncol = 2) +
  scale_x_continuous(breaks = pretty_breaks(6)) +
  scale_y_continuous(labels = percent) +
  scale_fill_brewer(palette = "Set2") +
  labs(title = "Within-year catch share by quarter",
       x = NULL, y = "Share")

Catch table

catch %>%
  pivot_wider(names_from = quarter, values_from = catch_t, values_fill = 0) %>%
  mutate(annual = Q1 + Q2 + Q3 + Q4) %>%
  arrange(member, fleet_canon, data_year) %>%
  mutate(across(c(Q1,Q2,Q3,Q4,annual), ~ round(.x))) %>%
  kable(caption = "Catch (tonnes) by quarter, with annual sum") %>%
  kable_styling(bootstrap_options = c("striped","hover"), font_size = 11) %>%
  scroll_box(height = "400px")
Catch (tonnes) by quarter, with annual sum
member fleet_canon mf data_year qtr Q1 Q2 Q3 Q4 annual
Chile F1 Chile F1 2015 Q1 29539 0 0 0 29539
Chile F1 Chile F1 2015 Q2 0 5311 0 0 5311
Chile F1 Chile F1 2015 Q3 0 0 7 0 7
Chile F1 Chile F1 2015 Q4 0 0 0 29 29
Chile F1 Chile F1 2016 Q1 6 0 0 0 6
Chile F1 Chile F1 2016 Q2 0 7102 0 0 7102
Chile F1 Chile F1 2016 Q3 0 0 12361 0 12361
Chile F1 Chile F1 2017 Q1 17992 0 0 0 17992
Chile F1 Chile F1 2017 Q2 0 10153 0 0 10153
Chile F1 Chile F1 2017 Q3 0 0 18 0 18
Chile F1 Chile F1 2017 Q4 0 0 0 175 175
Chile F1 Chile F1 2019 Q1 134 0 0 0 134
Chile F1 Chile F1 2019 Q2 0 1537 0 0 1537
Chile F1 Chile F1 2019 Q3 0 0 169 0 169
Chile F1 Chile F1 2019 Q4 0 0 0 12719 12719
Chile F1 Chile F1 2020 Q1 7649 0 0 0 7649
Chile F1 Chile F1 2020 Q2 0 27784 0 0 27784
Chile F1 Chile F1 2020 Q4 0 0 0 2567 2567
Chile F1 Chile F1 2021 Q1 35458 0 0 0 35458
Chile F1 Chile F1 2021 Q2 0 19334 0 0 19334
Chile F1 Chile F1 2021 Q3 0 0 212 0 212
Chile F1 Chile F1 2021 Q4 0 0 0 7140 7140
Chile F1 Chile F1 2022 Q1 10925 0 0 0 10925
Chile F1 Chile F1 2022 Q2 0 18102 0 0 18102
Chile F1 Chile F1 2022 Q3 0 0 12252 0 12252
Chile F1 Chile F1 2022 Q4 0 0 0 15202 15202
Chile F1 Chile F1 2023 Q1 60778 0 0 0 60778
Chile F1 Chile F1 2023 Q2 0 57325 0 0 57325
Chile F1 Chile F1 2023 Q3 0 0 3927 0 3927
Chile F1 Chile F1 2023 Q4 0 0 0 14074 14074
Chile F1 Chile F1 2024 Q1 86264 0 0 0 86264
Chile F1 Chile F1 2024 Q2 0 71058 0 0 71058
Chile F1 Chile F1 2024 Q3 0 0 14566 0 14566
Chile F1 Chile F1 2024 Q4 0 0 0 15722 15722
Chile F1 Chile F1 2025 Q1 32802 0 0 0 32802
Chile F2 Chile F2 2015 Q1 38872 0 0 0 38872
Chile F2 Chile F2 2015 Q2 0 145825 0 0 145825
Chile F2 Chile F2 2015 Q3 0 0 55453 0 55453
Chile F2 Chile F2 2015 Q4 0 0 0 10177 10177
Chile F2 Chile F2 2016 Q1 108489 0 0 0 108489
Chile F2 Chile F2 2016 Q2 0 133419 0 0 133419
Chile F2 Chile F2 2016 Q3 0 0 14182 0 14182
Chile F2 Chile F2 2017 Q1 57198 0 0 0 57198
Chile F2 Chile F2 2017 Q2 0 158755 0 0 158755
Chile F2 Chile F2 2017 Q3 0 0 66026 0 66026
Chile F2 Chile F2 2017 Q4 0 0 0 12084 12084
Chile F2 Chile F2 2018 Q1 146376 0 0 0 146376
Chile F2 Chile F2 2019 Q1 205560 0 0 0 205560
Chile F2 Chile F2 2019 Q2 0 165061 0 0 165061
Chile F2 Chile F2 2019 Q3 0 0 37077 0 37077
Chile F2 Chile F2 2019 Q4 0 0 0 20891 20891
Chile F2 Chile F2 2020 Q1 228190 0 0 0 228190
Chile F2 Chile F2 2020 Q2 0 128557 0 0 128557
Chile F2 Chile F2 2020 Q3 0 0 17550 0 17550
Chile F2 Chile F2 2020 Q4 0 0 0 11016 11016
Chile F2 Chile F2 2021 Q1 276076 0 0 0 276076
Chile F2 Chile F2 2021 Q2 0 217959 0 0 217959
Chile F2 Chile F2 2021 Q3 0 0 48888 0 48888
Chile F2 Chile F2 2021 Q4 0 0 0 53659 53659
Chile F2 Chile F2 2022 Q1 214628 0 0 0 214628
Chile F2 Chile F2 2022 Q2 0 221402 0 0 221402
Chile F2 Chile F2 2022 Q3 0 0 74873 0 74873
Chile F2 Chile F2 2022 Q4 0 0 0 67950 67950
Chile F2 Chile F2 2023 Q1 657516 0 0 0 657516
Chile F2 Chile F2 2023 Q2 0 545881 0 0 545881
Chile F2 Chile F2 2023 Q3 0 0 48614 0 48614
Chile F2 Chile F2 2023 Q4 0 0 0 228396 228396
Chile F2 Chile F2 2024 Q1 718491 0 0 0 718491
Chile F2 Chile F2 2024 Q2 0 491306 0 0 491306
Chile F2 Chile F2 2024 Q3 0 0 210989 0 210989
Chile F2 Chile F2 2024 Q4 0 0 0 319834 319834
Chile F2 Chile F2 2025 Q1 371493 0 0 0 371493
China F4 China F4 2016 Q1 4302 0 0 0 4302
China F4 China F4 2016 Q2 0 24402 0 0 24402
China F4 China F4 2016 Q3 0 0 9147 0 9147
China F4 China F4 2016 Q4 0 0 0 2564 2564
China F4 China F4 2017 Q2 0 9984 0 0 9984
China F4 China F4 2017 Q3 0 0 6601 0 6601
China F4 China F4 2018 Q1 1726 0 0 0 1726
China F4 China F4 2018 Q2 0 15571 0 0 15571
China F4 China F4 2018 Q3 0 0 7091 0 7091
China F4 China F4 2019 Q1 3017 0 0 0 3017
China F4 China F4 2019 Q2 0 9935 0 0 9935
China F4 China F4 2019 Q3 0 0 9762 0 9762
EU F4 EU F4 2016 Q1 6484 0 0 0 6484
EU F4 EU F4 2022 Q2 0 21341 0 0 21341
EU F4 EU F4 2022 Q3 0 0 18862 0 18862
EU F4 EU F4 2022 Q4 0 0 0 4061 4061
EU F4 EU F4 2023 Q2 0 570 0 0 570
EU F4 EU F4 2023 Q3 0 0 10500 0 10500
EU F4 EU F4 2025 Q2 0 205 0 0 205
EU F4 EU F4 2025 Q3 0 0 6450 0 6450
Ecuador F3 Ecuador F3 2016 Q4 0 0 0 289 289
Ecuador F3 Ecuador F3 2021 Q1 3 0 0 0 3
Ecuador F3 Ecuador F3 2021 Q2 0 1 0 0 1
Ecuador F3 Ecuador F3 2021 Q3 0 0 0 0 0
Ecuador F3 Ecuador F3 2021 Q4 0 0 0 4 4
Korea F4 Korea F4 2015 Q2 0 758 0 0 758
Korea F4 Korea F4 2015 Q3 0 0 4990 0 4990
Korea F4 Korea F4 2016 Q2 0 662 0 0 662
Korea F4 Korea F4 2016 Q3 0 0 3176 0 3176
Korea F4 Korea F4 2016 Q4 0 0 0 2593 2593
Korea F4 Korea F4 2017 Q2 0 91 0 0 91
Korea F4 Korea F4 2017 Q3 0 0 1144 0 1144
Korea F4 Korea F4 2024 Q2 0 0 0 0 0
Korea F4 Korea F4 2024 Q3 0 0 1557 0 1557
Korea F4 Korea F4 2024 Q4 0 0 0 240 240
Peru F3 Peru F3 2015 Q1 6602 0 0 0 6602
Peru F3 Peru F3 2015 Q2 0 6189 0 0 6189
Peru F3 Peru F3 2015 Q3 0 0 6256 0 6256
Peru F3 Peru F3 2015 Q4 0 0 0 3111 3111
Peru F3 Peru F3 2016 Q1 5094 0 0 0 5094
Peru F3 Peru F3 2016 Q2 0 4116 0 0 4116
Peru F3 Peru F3 2016 Q3 0 0 3751 0 3751
Peru F3 Peru F3 2016 Q4 0 0 0 2127 2127
Peru F3 Peru F3 2017 Q1 1480 0 0 0 1480
Peru F3 Peru F3 2017 Q2 0 1739 0 0 1739
Peru F3 Peru F3 2017 Q3 0 0 2832 0 2832
Peru F3 Peru F3 2017 Q4 0 0 0 2763 2763
Peru F3 Peru F3 2018 Q1 2824 0 0 0 2824
Peru F3 Peru F3 2018 Q2 0 2993 0 0 2993
Peru F3 Peru F3 2018 Q3 0 0 22074 0 22074
Peru F3 Peru F3 2018 Q4 0 0 0 26957 26957
Peru F3 Peru F3 2019 Q1 84679 0 0 0 84679
Peru F3 Peru F3 2019 Q2 0 6865 0 0 6865
Peru F3 Peru F3 2019 Q3 0 0 24101 0 24101
Peru F3 Peru F3 2019 Q4 0 0 0 20139 20139
Peru F3 Peru F3 2020 Q1 89237 0 0 0 89237
Peru F3 Peru F3 2020 Q2 0 4266 0 0 4266
Peru F3 Peru F3 2020 Q3 0 0 28039 0 28039
Peru F3 Peru F3 2020 Q4 0 0 0 18458 18458
Peru F3 Peru F3 2021 Q1 77972 0 0 0 77972
Peru F3 Peru F3 2021 Q2 0 13886 0 0 13886
Peru F3 Peru F3 2021 Q3 0 0 14533 0 14533
Peru F3 Peru F3 2021 Q4 0 0 0 17237 17237
Peru F3 Peru F3 2022 Q1 101707 0 0 0 101707
Peru F3 Peru F3 2022 Q2 0 23975 0 0 23975
Peru F3 Peru F3 2022 Q3 0 0 6 0 6
Peru F3 Peru F3 2022 Q4 0 0 0 33915 33915
Peru F3 Peru F3 2023 Q1 120792 0 0 0 120792
Peru F3 Peru F3 2023 Q2 0 9997 0 0 9997
Peru F3 Peru F3 2024 Q1 7047 0 0 0 7047
Peru F3 Peru F3 2024 Q2 0 56934 0 0 56934
Peru F3 Peru F3 2024 Q3 0 0 149603 0 149603
Peru F3 Peru F3 2024 Q4 0 0 0 3628 3628
Peru F3 Peru F3 2025 Q1 99725 0 0 0 99725
Peru F3 Peru F3 2025 Q2 0 7061 0 0 7061
Russia F4 Russia F4 2019 Q1 432 0 0 0 432
Russia F4 Russia F4 2019 Q2 0 2745 0 0 2745
Russia F4 Russia F4 2019 Q3 0 0 6246 0 6246
Russia F4 Russia F4 2020 Q3 0 0 3378 0 3378
Russia F4 Russia F4 2020 Q4 0 0 0 1867 1867
Russia F4 Russia F4 2021 Q1 156 0 0 0 156
Russia F4 Russia F4 2021 Q2 0 4523 0 0 4523
Russia F4 Russia F4 2021 Q3 0 0 7472 0 7472
Russia F4 Russia F4 2022 Q2 0 9136 0 0 9136
Russia F4 Russia F4 2022 Q3 0 0 9111 0 9111
Russia F4 Russia F4 2022 Q4 0 0 0 11660 11660
Russia F4 Russia F4 2023 Q1 2774 0 0 0 2774
Russia F4 Russia F4 2023 Q2 0 10099 0 0 10099
Russia F4 Russia F4 2023 Q3 0 0 15131 0 15131
Russia F4 Russia F4 2023 Q4 0 0 0 13137 13137
Russia F4 Russia F4 2024 Q2 0 261 0 0 261
Russia F4 Russia F4 2024 Q3 0 0 7968 0 7968
Russia F4 Russia F4 2024 Q4 0 0 0 3881 3881

3. Sampling effort

sampling <- df %>%
  filter(variable == "sampling",
         metric_detail %in% c("samples","samples_1","samples_2","measured","aged",
                              "total_of_sample")) %>%
  mutate(stream = case_when(
    metric_detail == "aged" ~ "aged",
    metric_detail == "total_of_sample" ~ "sampled_weight_t",
    metric_detail %in% c("measured","samples","samples_1","samples_2") ~ "measured"
  )) %>%
  group_by(member, fleet_canon, mf, data_year, quarter, qtr, stream) %>%
  summarise(value = sum(value, na.rm = TRUE), .groups = "drop")

Annual measured / aged

sampling %>%
  filter(quarter == "all", stream %in% c("measured","aged")) %>%
  mutate(year_f = factor(data_year)) %>%
  ggplot(aes(year_f, value, fill = stream)) +
  geom_col(position = position_dodge(width = 0.8), width = 0.75) +
  facet_wrap(~ mf, scales = "free_y", ncol = 2) +
  scale_y_continuous(labels = comma) +
  scale_fill_brewer(palette = "Set1") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1)) +
  labs(title = "Annual sampling effort: number of fish measured / aged",
       x = NULL, y = "Fish (n)", fill = NULL)

Annual sampled weight

sw <- sampling %>%
  filter(quarter == "all", stream == "sampled_weight_t")

if (nrow(sw)) {
  sw %>%
    mutate(year_f = factor(data_year)) %>%
    ggplot(aes(year_f, value, fill = mf)) +
    geom_col(width = 0.75) +
    facet_wrap(~ mf, scales = "free_y", ncol = 2) +
    scale_y_continuous(labels = comma) +
    guides(fill = "none") +
    theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1)) +
    labs(title = "Annual sampled weight (tonnes)",
         x = NULL, y = "Sampled weight (tonnes)")
} else {
  cat("No annual sampled weight data after filtering.\n")
}

Year × quarter (measured)

sampling %>%
  filter(stream == "measured", quarter %in% c("Q1","Q2","Q3","Q4")) %>%
  ggplot(aes(data_year, qtr, fill = value)) +
  geom_tile() +
  facet_wrap(~ mf, ncol = 2) +
  scale_fill_viridis_c(option = "C", labels = comma, name = "fish") +
  labs(title = "Number of fish measured by year × quarter",
       x = NULL, y = "Quarter")

Year × quarter (aged)

sampling %>%
  filter(stream == "aged", quarter %in% c("Q1","Q2","Q3","Q4")) %>%
  ggplot(aes(data_year, qtr, fill = value)) +
  geom_tile() +
  facet_wrap(~ mf, ncol = 2) +
  scale_fill_viridis_c(option = "C", labels = comma, name = "fish") +
  labs(title = "Number of fish aged by year × quarter",
       x = NULL, y = "Quarter")

Year × quarter (sampled weight)

sw_q <- sampling %>%
  filter(stream == "sampled_weight_t", quarter %in% c("Q1","Q2","Q3","Q4"))

if (nrow(sw_q)) {
  ggplot(sw_q, aes(data_year, qtr, fill = value)) +
    geom_tile() +
    facet_wrap(~ mf, ncol = 2) +
    scale_fill_viridis_c(option = "C", labels = comma, name = "tonnes") +
    labs(title = "Sampled weight (tonnes) by year × quarter",
         x = NULL, y = "Quarter")
} else {
  cat("No quarterly sampled-weight data after filtering.\n")
}

Sampling table

sampling %>%
  filter(quarter == "all") %>%
  pivot_wider(names_from = stream, values_from = value) %>%
  arrange(member, fleet_canon, data_year) %>%
  mutate(across(where(is.numeric) & !data_year, ~ round(.x, 1))) %>%
  kable(caption = "Annual sampling: fish measured / aged and sampled weight (tonnes)") %>%
  kable_styling(bootstrap_options = c("striped","hover"), font_size = 11) %>%
  scroll_box(height = "400px")
Annual sampling: fish measured / aged and sampled weight (tonnes)
member fleet_canon mf data_year quarter qtr aged measured sampled_weight_t
Chile F1 Chile F1 2015 all all 54 118 25982.3
Chile F1 Chile F1 2016 all all NA 38 NA
Chile F1 Chile F1 2017 all all 230 7882 NA
Chile F1 Chile F1 2019 all all 123 409 13258.8
Chile F1 Chile F1 2020 all all 175 646 38338.2
Chile F1 Chile F1 2021 all all 342 1175 62144.7
Chile F1 Chile F1 2022 all all 551 2375 56481.0
Chile F1 Chile F1 2023 all all 1376 6944 136104.6
Chile F1 Chile F1 2024 all all 1954 11664 187609.5
Chile F1 Chile F1 2025 all all 207 1289 32802.4
Chile F2 Chile F2 2015 all all 1995 7849 132943.5
Chile F2 Chile F2 2016 all all 414 4231 NA
Chile F2 Chile F2 2017 all all 1872 50776 NA
Chile F2 Chile F2 2018 all all 616 17136 NA
Chile F2 Chile F2 2019 all all 1926 19836 438567.6
Chile F2 Chile F2 2020 all all 1578 25215 388214.8
Chile F2 Chile F2 2021 all all 1833 28997 596290.8
Chile F2 Chile F2 2022 all all 1725 21638 578853.0
Chile F2 Chile F2 2023 all all 4064 71164 1480406.9
Chile F2 Chile F2 2024 all all 3910 65336 1740620.0
Chile F2 Chile F2 2025 all all 427 10604 371493.1
China F4 China F4 2016 all all 600 2660 1330.0
China F4 China F4 2018 all all NA 24378 6.1
China F4 China F4 2019 all all NA 23802 12.6
EU F4 EU F4 2016 all all NA 7699 3819.0
EU F4 EU F4 2017 all all 1625 13943 4817.0
EU F4 EU F4 2018 all all 1000 7542 4120.0
EU F4 EU F4 2019 all all 251 5204 5185.0
EU F4 EU F4 2021 all all 212 11929 5808.8
EU F4 EU F4 2022 all all 535 21416 19619.8
EU F4 EU F4 2023 all all 794 28110 7.7
EU F4 EU F4 2024 all all 67 8844 17772.9
EU F4 EU F4 2025 all all NA 11220 NA
Korea F4 Korea F4 2014 all all NA NA 0.1
Korea F4 Korea F4 2015 all all NA 4482 1.0
Korea F4 Korea F4 2016 all all NA 16340 2.2
Korea F4 Korea F4 2017 all all NA 3134 0.5
Korea F4 Korea F4 2019 all all NA 14314 1.1
Korea F4 Korea F4 2024 all all NA 13198 NA
Peru F3 Peru F3 2015 all all NA 3169 110.0
Peru F3 Peru F3 2017 all all NA 19064 233.1
Peru F3 Peru F3 2018 all all NA 43581 20428.6
Peru F3 Peru F3 2019 all all NA 60454 8408.8
Peru F3 Peru F3 2020 all all NA 67113 55984.3
Peru F3 Peru F3 2021 all all NA 48704 13229.7
Peru F3 Peru F3 2022 all all NA 54409 24270.6
Peru F3 Peru F3 2023 all all NA 40134 34655.7
Peru F3 Peru F3 2024 all all NA 46198 23282.9
Peru F3 Peru F3 2025 all all NA 23010 11750.9
Russia F4 Russia F4 2019 all all 1548 24833 4114.0
Russia F4 Russia F4 2020 all all 529 18373 3935.0
Russia F4 Russia F4 2021 all all 778 22614 7.9
Russia F4 Russia F4 2022 all all 2226 36627 12466.3
Russia F4 Russia F4 2023 all all 2023 61922 15040.0
Russia F4 Russia F4 2024 all all 698 13184 1959.0

4. Length composition

We use only the standard template’s length-frequency rows (data_source == "STANDARD_TEMPLATE").

lfd_q <- df %>%
  filter(variable == "length_freq",
         data_source == "STANDARD_TEMPLATE",
         quarter %in% c("Q1","Q2","Q3","Q4"),
         !is.na(length_cm)) %>%
  group_by(member, fleet_canon, mf, data_year, quarter, qtr, length_cm) %>%
  summarise(n_thousands = sum(value, na.rm = TRUE), .groups = "drop")

lfd_yr <- df %>%
  filter(variable == "length_freq",
         data_source == "STANDARD_TEMPLATE",
         quarter == "all",
         !is.na(length_cm)) %>%
  group_by(member, fleet_canon, mf, data_year, length_cm) %>%
  summarise(n_thousands = sum(value, na.rm = TRUE), .groups = "drop")

# Numbers-weighted-quantile helper
wq <- function(x, w, p) {
  ord <- order(x)
  x <- x[ord]; w <- w[ord]
  if (!length(x) || !is.finite(sum(w)) || sum(w) <= 0) return(NA_real_)
  cw <- cumsum(w) / sum(w)
  approx(cw, x, xout = p, ties = "ordered", rule = 2)$y
}

lfd_stats_q <- lfd_q %>%
  group_by(member, fleet_canon, mf, data_year, quarter, qtr) %>%
  summarise(n_total_thousands = sum(n_thousands),
            mean_len = sum(length_cm * n_thousands) / sum(n_thousands),
            p25_len  = wq(length_cm, n_thousands, 0.25),
            median_len = wq(length_cm, n_thousands, 0.50),
            p75_len  = wq(length_cm, n_thousands, 0.75),
            mode_len = length_cm[which.max(n_thousands)],
            .groups = "drop")

Annual LFD curves

ggplot(lfd_yr, aes(length_cm, n_thousands, group = data_year, color = data_year)) +
  geom_line(alpha = 0.6, linewidth = 0.4) +
  facet_wrap(~ mf, scales = "free_y", ncol = 2) +
  scale_color_viridis_c() +
  scale_y_continuous(labels = comma) +
  labs(title = "Annual length-frequency (one line per year)",
       x = "Fork length (cm)", y = "Numbers (thousands)", color = "Year")

Quarterly LFD curves

ggplot(lfd_q, aes(length_cm, n_thousands, group = data_year, color = data_year)) +
  geom_line(alpha = 0.6, linewidth = 0.35) +
  facet_grid(mf ~ qtr, scales = "free_y") +
  scale_color_viridis_c() +
  scale_y_continuous(labels = comma) +
  labs(title = "Quarterly length-frequency (one line per year)",
       x = "Fork length (cm)", y = "Numbers (thousands)", color = "Year")

Mean length over time, by quarter

ggplot(lfd_stats_q, aes(data_year, mean_len, color = qtr)) +
  geom_line() + geom_point(size = 1.2) +
  facet_wrap(~ mf, ncol = 2) +
  scale_color_brewer(palette = "Set1", name = "Quarter") +
  scale_x_continuous(breaks = pretty_breaks(6)) +
  labs(title = "Numbers-weighted mean length per quarter, by year",
       x = NULL, y = "Mean fork length (cm)")

Composition (proportion) by year × quarter, per fleet

For each (member, fleet) the length-frequency distribution within each (year, quarter) cell, normalized to a proportion (sums to 1 per year × quarter). Y-axis is proportion, X-axis is fork length, panels are year × quarter. Lets you see shifts in the distribution within a year and across years.

# Compute per (mf, year, quarter) proportion-at-length
lfd_prop <- lfd_q %>%
  group_by(member, fleet_canon, mf, data_year, quarter, qtr) %>%
  mutate(prop = n_thousands / sum(n_thousands, na.rm = TRUE)) %>%
  ungroup() %>%
  filter(is.finite(prop), prop > 0)

plot_lfd_prop <- function(this_mf) {
  d <- lfd_prop %>% filter(mf == this_mf)
  if (!nrow(d)) {
    cat("No data for", this_mf, "\n"); return(invisible(NULL))
  }
  ggplot(d, aes(length_cm, prop)) +
    geom_col(fill = "steelblue", width = 1) +
    facet_grid(data_year ~ qtr, switch = "y") +
    scale_y_continuous(labels = percent_format(accuracy = 1),
                       breaks = pretty_breaks(2)) +
    scale_x_continuous(breaks = pretty_breaks(5)) +
    theme(strip.text.y.left = element_text(angle = 0),
          panel.spacing = unit(0.2, "lines")) +
    labs(title = paste0("Length composition (proportion) — ", this_mf),
         x = "Fork length (cm)", y = "Proportion")
}

Chile F1

plot_lfd_prop("Chile F1")

Chile F2

plot_lfd_prop("Chile F2")

China F4

plot_lfd_prop("China F4")

Ecuador F3

plot_lfd_prop("Ecuador F3")

EU F4

plot_lfd_prop("EU F4")

Korea F4

plot_lfd_prop("Korea F4")

Peru F3

plot_lfd_prop("Peru F3")

Russia F4

plot_lfd_prop("Russia F4")

Length range with non-zero counts

lfd_range <- lfd_yr %>%
  filter(n_thousands > 0) %>%
  group_by(member, fleet_canon, mf, data_year) %>%
  summarise(min_len = min(length_cm), max_len = max(length_cm),
            n_lengths = n_distinct(length_cm),
            total_thousands = sum(n_thousands),
            .groups = "drop")

ggplot(lfd_range, aes(data_year)) +
  geom_linerange(aes(ymin = min_len, ymax = max_len), color = "steelblue") +
  geom_point(aes(y = min_len), size = 1.5, color = "steelblue") +
  geom_point(aes(y = max_len), size = 1.5, color = "steelblue") +
  facet_wrap(~ mf, ncol = 2) +
  geom_hline(yintercept = c(10, 75), linetype = "dashed", color = "red") +
  labs(title = "Length range observed per year",
       subtitle = "Red dashed lines = biologically plausible range (10–75 cm)",
       x = NULL, y = "Fork length (cm)")

Length-out-of-range flags

lfd_flags <- lfd_yr %>%
  filter(n_thousands > 0, (length_cm < 10 | length_cm > 75)) %>%
  arrange(desc(n_thousands))

if (nrow(lfd_flags)) {
  lfd_flags %>%
    head(50) %>%
    kable(caption = paste0("Lengths outside 10–75 cm with non-zero counts (top 50 of ",
                          nrow(lfd_flags), ")"), digits = 1) %>%
    kable_styling(bootstrap_options = c("striped","hover"), font_size = 10) %>%
    scroll_box(height = "300px")
} else {
  cat("No length values outside 10–75 cm.\n")
}
Lengths outside 10–75 cm with non-zero counts (top 50 of 13)
member fleet_canon mf data_year length_cm n_thousands
Chile F1 Chile F1 2019 9 244511.1
Chile F1 Chile F1 2019 8 181100.9
Chile F2 Chile F2 2019 9 25645.7
Chile F2 Chile F2 2019 8 20516.6
Chile F1 Chile F1 2023 9 3171.2
Chile F1 Chile F1 2023 8 1934.5
Chile F1 Chile F1 2015 9 311.3
Chile F1 Chile F1 2023 7 281.2
Chile F1 Chile F1 2024 9 214.2
Peru F3 Peru F3 2021 9 64.6
Chile F1 Chile F1 2015 8 51.9
Chile F1 Chile F1 2021 9 29.8
Peru F3 Peru F3 2021 8 2.8

Year-on-year LFD-total scale shifts

Successive years’ LFD totals differing by more than 100× usually indicate a unit/scaling change between submissions.

lfd_year_totals <- lfd_yr %>%
  group_by(member, fleet_canon, mf, data_year) %>%
  summarise(total_thousands = sum(n_thousands), .groups = "drop") %>%
  arrange(member, fleet_canon, data_year) %>%
  group_by(member, fleet_canon, mf) %>%
  mutate(prev = lag(total_thousands),
         ratio = ifelse(prev > 0, total_thousands / prev, NA_real_)) %>%
  ungroup()

scale_shifts <- lfd_year_totals %>% filter(!is.na(ratio), (ratio > 100 | ratio < 1/100))

if (nrow(scale_shifts)) {
  scale_shifts %>%
    arrange(desc(abs(log10(ratio)))) %>%
    mutate(across(c(total_thousands, prev), ~ formatC(.x, format="e", digits=2)),
           ratio = round(ratio, 2)) %>%
    kable(caption = "LFD-total ratio outside [1/100, 100]") %>%
    kable_styling(bootstrap_options = c("striped","hover"), font_size = 10) %>%
    scroll_box(height = "300px")
} else {
  cat("No suspicious 100x year-on-year LFD scale changes.\n")
}
LFD-total ratio outside [1/100, 100]
member fleet_canon mf data_year total_thousands prev ratio
Chile F2 Chile F2 2017 6.50e+08 2.76e+05 2350.41
Chile F2 Chile F2 2020 6.38e+05 9.39e+08 0.00
Chile F1 Chile F1 2017 1.91e+08 1.63e+05 1169.87
Chile F1 Chile F1 2020 1.23e+05 3.11e+07 0.00

5. Mean weight at length

The standard template’s WLK sheet is reported by quarter only — there is no annual aggregate row. So this section shows quarterly views only.

wlk <- df %>%
  filter(variable == "weight_at_length",
         data_source == "STANDARD_TEMPLATE",
         !is.na(length_cm), value > 0) %>%
  group_by(member, fleet_canon, mf, data_year, quarter, qtr, length_cm) %>%
  summarise(mean_w_kg = mean(value), .groups = "drop")

Curves by quarter

wlk %>%
  filter(quarter %in% c("Q1","Q2","Q3","Q4")) %>%
  ggplot(aes(length_cm, mean_w_kg, group = data_year, color = data_year)) +
  geom_line(alpha = 0.6, linewidth = 0.35) +
  facet_grid(mf ~ qtr, scales = "free_y") +
  scale_color_viridis_c() +
  labs(title = "Mean weight-at-length by quarter",
       x = "Fork length (cm)", y = "Mean weight (kg)", color = "Year")

6. Numbers at age (CANUM)

canum_q <- df %>%
  filter(variable == "numbers_at_age",
         metric_detail == "numbers_at_age",
         quarter %in% c("Q1","Q2","Q3","Q4"),
         !is.na(age)) %>%
  group_by(member, fleet_canon, mf, data_year, quarter, qtr, age) %>%
  summarise(n_thousands = sum(value), .groups = "drop")

canum_yr <- df %>%
  filter(variable == "numbers_at_age",
         metric_detail == "numbers_at_age",
         quarter == "all",
         !is.na(age)) %>%
  group_by(member, fleet_canon, mf, data_year, age) %>%
  summarise(n_thousands = sum(value), .groups = "drop")

Annual age distributions

ggplot(canum_yr, aes(age, n_thousands, group = data_year, color = data_year)) +
  geom_line(alpha = 0.6) +
  facet_wrap(~ mf, scales = "free_y", ncol = 2) +
  scale_color_viridis_c() +
  scale_x_continuous(breaks = 0:12) +
  scale_y_continuous(labels = comma) +
  labs(title = "Numbers at age — annual",
       x = "Age", y = "Numbers (thousands)", color = "Year")

Quarterly age distributions

ggplot(canum_q, aes(age, n_thousands, group = data_year, color = data_year)) +
  geom_line(alpha = 0.6, linewidth = 0.35) +
  facet_grid(mf ~ qtr, scales = "free_y") +
  scale_color_viridis_c() +
  scale_x_continuous(breaks = 0:12) +
  scale_y_continuous(labels = comma) +
  labs(title = "Numbers at age — quarterly",
       x = "Age", y = "Numbers (thousands)", color = "Year")

Mean age by quarter

mean_age_q <- canum_q %>%
  group_by(member, fleet_canon, mf, data_year, quarter, qtr) %>%
  summarise(mean_age = sum(age * n_thousands, na.rm=TRUE) / sum(n_thousands, na.rm=TRUE),
            .groups = "drop")

ggplot(mean_age_q, aes(data_year, mean_age, color = qtr)) +
  geom_line() + geom_point(size = 1.2) +
  facet_wrap(~ mf, ncol = 2) +
  scale_color_brewer(palette = "Set1", name = "Quarter") +
  scale_x_continuous(breaks = pretty_breaks(6)) +
  labs(title = "Mean age in catch over time, by quarter",
       x = NULL, y = "Mean age (years)")

Composition (proportion) by year × quarter, per fleet

For each (member, fleet) the age distribution within each (year, quarter) cell, normalized to a proportion (sums to 1 per year × quarter). Y-axis is proportion, X-axis is age, panels are year × quarter. Lets you visually track cohort progression.

canum_prop <- canum_q %>%
  group_by(member, fleet_canon, mf, data_year, quarter, qtr) %>%
  mutate(prop = n_thousands / sum(n_thousands, na.rm = TRUE)) %>%
  ungroup() %>%
  filter(is.finite(prop), prop > 0)

plot_canum_prop <- function(this_mf) {
  d <- canum_prop %>% filter(mf == this_mf)
  if (!nrow(d)) {
    cat("No data for", this_mf, "\n"); return(invisible(NULL))
  }
  ggplot(d, aes(age, prop)) +
    geom_col(fill = "darkgreen", width = 0.85) +
    facet_grid(data_year ~ qtr, switch = "y") +
    scale_y_continuous(labels = percent_format(accuracy = 1),
                       breaks = pretty_breaks(2)) +
    scale_x_continuous(breaks = 0:12) +
    theme(strip.text.y.left = element_text(angle = 0),
          panel.spacing = unit(0.2, "lines")) +
    labs(title = paste0("Age composition (proportion) — ", this_mf),
         x = "Age", y = "Proportion")
}

Chile F1

plot_canum_prop("Chile F1")

Chile F2

plot_canum_prop("Chile F2")

China F4

plot_canum_prop("China F4")

EU F4

plot_canum_prop("EU F4")

Peru F3

plot_canum_prop("Peru F3")

Russia F4

plot_canum_prop("Russia F4")

Age range with non-zero numbers

age_range <- canum_yr %>%
  filter(n_thousands > 0) %>%
  group_by(member, fleet_canon, mf, data_year) %>%
  summarise(min_age = min(age), max_age = max(age),
            n_ages = n_distinct(age),
            total_thousands = round(sum(n_thousands)),
            .groups = "drop") %>%
  arrange(member, fleet_canon, data_year)

age_range %>%
  kable(caption = "Age range and total numbers per (mf × year)") %>%
  kable_styling(bootstrap_options = c("striped","hover"), font_size = 11) %>%
  scroll_box(height = "400px")
Age range and total numbers per (mf × year)
member fleet_canon mf data_year min_age max_age n_ages total_thousands
Chile F1 Chile F1 2015 1 8 8 163287
Chile F1 Chile F1 2017 1 12 12 191021732
Chile F1 Chile F1 2019 0 12 13 31113983
Chile F1 Chile F1 2020 2 12 10 122977
Chile F1 Chile F1 2021 0 12 13 121214
Chile F1 Chile F1 2022 0 12 12 92941
Chile F1 Chile F1 2023 0 12 13 214187
Chile F1 Chile F1 2024 0 11 12 266236
Chile F1 Chile F1 2025 1 12 12 47784
Chile F2 Chile F2 2015 1 12 12 413967
Chile F2 Chile F2 2016 1 12 11 276589
Chile F2 Chile F2 2017 1 12 12 649653564
Chile F2 Chile F2 2018 3 12 10 353682021
Chile F2 Chile F2 2019 0 12 13 939068051
Chile F2 Chile F2 2020 2 12 11 637768
Chile F2 Chile F2 2021 1 12 12 1010447
Chile F2 Chile F2 2022 0 12 13 732050
Chile F2 Chile F2 2023 1 12 12 2072759
Chile F2 Chile F2 2024 2 12 11 2955686
Chile F2 Chile F2 2025 2 12 11 710427
China F4 China F4 2016 3 9 7 47914
EU F4 EU F4 2016 2 12 8 25235
EU F4 EU F4 2017 1 8 8 90481
EU F4 EU F4 2018 2 10 9 13336
EU F4 EU F4 2019 2 10 9 11657
EU F4 EU F4 2021 1 6 6 109769
EU F4 EU F4 2022 2 12 11 227023
EU F4 EU F4 2023 2 12 11 207262
EU F4 EU F4 2024 1 9 9 9693
Peru F3 Peru F3 2017 0 7 8 81340
Peru F3 Peru F3 2018 1 7 7 204490
Peru F3 Peru F3 2019 1 7 7 340229
Peru F3 Peru F3 2020 1 7 7 284476
Peru F3 Peru F3 2021 0 12 9 173103
Peru F3 Peru F3 2022 1 12 8 222692
Peru F3 Peru F3 2023 1 12 8 196917
Peru F3 Peru F3 2024 1 12 8 227336
Peru F3 Peru F3 2025 1 12 8 108686
Russia F4 Russia F4 2019 1 12 12 44609
Russia F4 Russia F4 2020 1 11 11 24947
Russia F4 Russia F4 2021 0 12 12 49059
Russia F4 Russia F4 2022 0 12 13 131953
Russia F4 Russia F4 2023 1 12 12 167108
Russia F4 Russia F4 2024 0 11 12 40347

7. Mean length-at-age and weight-at-age

mlaa <- df %>%
  filter(variable == "mean_length_at_age",
         metric_detail == "mean_length_cm",
         !is.na(age)) %>%
  group_by(member, fleet_canon, mf, data_year, quarter, qtr, age) %>%
  summarise(mean_len = mean(value, na.rm = TRUE), .groups = "drop")

mwaa <- df %>%
  filter(variable == "mean_weight_at_age",
         metric_detail == "mean_weight",
         !is.na(age)) %>%
  group_by(member, fleet_canon, mf, data_year, quarter, qtr, age) %>%
  summarise(mean_w_kg = mean(value, na.rm = TRUE), .groups = "drop")

Mean length-at-age (annual)

mlaa %>%
  filter(quarter == "all") %>%
  ggplot(aes(age, mean_len, group = data_year, color = data_year)) +
  geom_line(alpha = 0.6) +
  facet_wrap(~ mf, scales = "free_y", ncol = 2) +
  scale_color_viridis_c() +
  scale_x_continuous(breaks = 0:12) +
  labs(title = "Mean length at age (annual)",
       x = "Age", y = "Mean length (cm)", color = "Year")

Mean length-at-age (quarterly)

mlaa %>%
  filter(quarter %in% c("Q1","Q2","Q3","Q4")) %>%
  ggplot(aes(age, mean_len, group = data_year, color = data_year)) +
  geom_line(alpha = 0.6, linewidth = 0.35) +
  facet_grid(mf ~ qtr, scales = "free_y") +
  scale_color_viridis_c() +
  scale_x_continuous(breaks = 0:12) +
  labs(title = "Mean length at age — by quarter",
       x = "Age", y = "Mean length (cm)", color = "Year")

Mean weight-at-age (annual)

mwaa %>%
  filter(quarter == "all") %>%
  ggplot(aes(age, mean_w_kg, group = data_year, color = data_year)) +
  geom_line(alpha = 0.6) +
  facet_wrap(~ mf, scales = "free_y", ncol = 2) +
  scale_color_viridis_c() +
  scale_x_continuous(breaks = 0:12) +
  labs(title = "Mean weight at age (annual)",
       x = "Age", y = "Mean weight (kg)", color = "Year")

Mean weight-at-age (quarterly)

mwaa %>%
  filter(quarter %in% c("Q1","Q2","Q3","Q4")) %>%
  ggplot(aes(age, mean_w_kg, group = data_year, color = data_year)) +
  geom_line(alpha = 0.6, linewidth = 0.35) +
  facet_grid(mf ~ qtr, scales = "free_y") +
  scale_color_viridis_c() +
  scale_x_continuous(breaks = 0:12) +
  labs(title = "Mean weight at age — by quarter",
       x = "Age", y = "Mean weight (kg)", color = "Year")