From 54341cb062790cd781f7d81aa563fa64edac370c Mon Sep 17 00:00:00 2001 From: Sam Schiano <125507018+Schiano-NOAA@users.noreply.github.com> Date: Wed, 4 Nov 2026 16:12:29 -0500 Subject: [PATCH 1/3] feat: composition plots --- NAMESPACE | 10 + R/conout_ss3.R | 14 + R/data.R | 66 +- R/plot_composition.R | 705 ++++++++++++++++ R/plot_composition_diagnostics.R | 523 ++++++++++++ R/utils_composition.R | 785 ++++++++++++++++++ data-raw/bigskate_comps.R | 36 + data/bigskate_caal.rda | Bin 0 -> 78488 bytes data/bigskate_lencomp.rda | Bin 0 -> 58316 bytes inst/WORDLIST | 50 ++ man/bigskate_caal.Rd | 43 + man/bigskate_lencomp.Rd | 48 ++ man/plot_composition_andre.Rd | 84 ++ man/plot_composition_bubble.Rd | 87 ++ man/plot_composition_distribution.Rd | 85 ++ man/plot_composition_francis.Rd | 90 ++ man/plot_composition_residual_distribution.Rd | 62 ++ man/plot_composition_residual_pattern.Rd | 66 ++ man/plot_composition_residuals.Rd | 41 + man/plot_composition_samples.Rd | 92 ++ man/plot_composition_sexratio.Rd | 78 ++ man/prepare_composition_data.Rd | 116 +++ pkgdown/_pkgdown.yml | 2 + tests/testthat/test-convert_output.R | 20 + tests/testthat/test-plot_composition.R | 244 ++++++ .../test-plot_composition_diagnostics.R | 198 +++++ vignettes/composition_diagnostics.Rmd | 274 ++++++ 27 files changed, 3818 insertions(+), 1 deletion(-) create mode 100644 R/plot_composition.R create mode 100644 R/plot_composition_diagnostics.R create mode 100644 R/utils_composition.R create mode 100644 data-raw/bigskate_comps.R create mode 100644 data/bigskate_caal.rda create mode 100644 data/bigskate_lencomp.rda create mode 100644 man/bigskate_caal.Rd create mode 100644 man/bigskate_lencomp.Rd create mode 100644 man/plot_composition_andre.Rd create mode 100644 man/plot_composition_bubble.Rd create mode 100644 man/plot_composition_distribution.Rd create mode 100644 man/plot_composition_francis.Rd create mode 100644 man/plot_composition_residual_distribution.Rd create mode 100644 man/plot_composition_residual_pattern.Rd create mode 100644 man/plot_composition_residuals.Rd create mode 100644 man/plot_composition_samples.Rd create mode 100644 man/plot_composition_sexratio.Rd create mode 100644 man/prepare_composition_data.Rd create mode 100644 tests/testthat/test-plot_composition.R create mode 100644 tests/testthat/test-plot_composition_diagnostics.R create mode 100644 vignettes/composition_diagnostics.Rmd diff --git a/NAMESPACE b/NAMESPACE index f7fd3c70..75639dd1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -14,6 +14,15 @@ export(plot_abundance_at_age) export(plot_biomass) export(plot_biomass_at_age) export(plot_catch_comp) +export(plot_composition_andre) +export(plot_composition_bubble) +export(plot_composition_distribution) +export(plot_composition_francis) +export(plot_composition_residual_distribution) +export(plot_composition_residual_pattern) +export(plot_composition_residuals) +export(plot_composition_samples) +export(plot_composition_sexratio) export(plot_discard) export(plot_fishing_mortality) export(plot_index) @@ -27,6 +36,7 @@ export(plot_spawning_biomass) export(plot_spr) export(plot_stock_recruitment) export(plot_timeseries) +export(prepare_composition_data) export(process_data) export(process_table) export(reference_line) diff --git a/R/conout_ss3.R b/R/conout_ss3.R index 0eb02cc6..a98e7198 100644 --- a/R/conout_ss3.R +++ b/R/conout_ss3.R @@ -1268,4 +1268,18 @@ conout_ss3 <- function( TRUE ~ NA_character_ ) ) + # Composition data by bin from r4ss (lendbase, agedbase, condbase) + if (!is.character(file)) { + comp_rows <- ss3_composition_rows( + dat, + fleet_names = fleet_names, + start_year = start_year, + end_year = end_year, + columns = names(out_new) + ) + if (!is.null(comp_rows)) { + cli::cli_alert("Processing composition data (lendbase, agedbase, condbase)") + out_new <- rbind(out_new, comp_rows) + } + } } diff --git a/R/data.R b/R/data.R index 116a3b5a..57c338d4 100644 --- a/R/data.R +++ b/R/data.R @@ -215,4 +215,68 @@ #' data(ss3_simple) #' head(ss3_simple) #' unique(ss3_simple$label) -"ss3_simple" \ No newline at end of file +"ss3_simple" + +#' Big Skate length composition data +#' +#' Length compositions and fits from the 2019 Big Skate stock assessment for +#' the U.S. West Coast (`BigSkate_2019` in the Stock Synthesis test models), +#' the model used for the r4ss example plots at +#' \url{https://r4ss.github.io/r4ss/BigSkate/}. The data are the `lendbase` +#' rows of [convert_output()] applied to the output of `r4ss::SS_output()`. +#' See `data-raw/bigskate_comps.R`. +#' +#' Compositions are sex-specific, with female and male proportions summing to +#' one within each year, fleet, and partition. `Fishery_current` has separate +#' retained and discarded compositions. +#' +#' @format A tibble in the standard [convert_output()] format with 14504 rows +#' and 35 columns. The columns used for compositions are: +#' \describe{ +#' \item{label}{"length_composition_observed", "length_composition_predicted", +#' "length_composition_pearson_residual" (as reported by Stock Synthesis), +#' or "length_composition_sample_size" (adjusted input sample size, +#' `Nsamp_adj` in r4ss)} +#' \item{estimate}{Value of the label} +#' \item{module_name}{`"lendbase"`} +#' \item{year}{Year of the observation} +#' \item{fleet}{Fleet name: "Fishery_current", "WCGBTS", or "Triennial"} +#' \item{sex}{"female" or "male"} +#' \item{part}{Partition: 0 = whole catch, 1 = discard, 2 = retained} +#' \item{length_bins}{Lower edge of the length bin (cm)} +#' } +#' @source Taylor, I.G., Gertseva, V., Stephens, A., and Bizzarro, J. (2019). Status of +#' Big Skate (Beringraja binoculata) off the U.S. Pacific Coast in 2019. +#' Pacific Fishery Management Council, Portland, OR. +#' @seealso [bigskate_caal], [prepare_composition_data()], [plot_composition_distribution()] +"bigskate_lencomp" + +#' Big Skate conditional age-at-length data +#' +#' Conditional age-at-length data and fits from the 2019 Big Skate stock +#' assessment (see [bigskate_lencomp]). The data are the `condbase` rows of +#' [convert_output()] applied to the output of `r4ss::SS_output()`. Each age +#' composition is for one sex and one length bin, and sums to one. See +#' `data-raw/bigskate_comps.R`. +#' +#' @format A tibble in the standard [convert_output()] format with 26240 rows +#' and 35 columns. The columns used for compositions are: +#' \describe{ +#' \item{label}{"conditional_age_composition_observed", +#' "conditional_age_composition_predicted", +#' "conditional_age_composition_pearson_residual", or +#' "conditional_age_composition_sample_size"} +#' \item{estimate}{Value of the label} +#' \item{module_name}{`"condbase"`} +#' \item{year}{Year of the observation} +#' \item{fleet}{Fleet name: "Fishery_current" or "WCGBTS"} +#' \item{sex}{"female" or "male"} +#' \item{part}{Partition: 0 = whole catch, 1 = discard, 2 = retained} +#' \item{length_bins}{Length bin the ages are conditioned on (`Lbin_lo` in r4ss)} +#' \item{age}{Age} +#' } +#' @source Taylor, I.G., Gertseva, V., Stephens, A., and Bizzarro, J. (2019). Status of +#' Big Skate (Beringraja binoculata) off the U.S. Pacific Coast in 2019. +#' Pacific Fishery Management Council, Portland, OR. +#' @seealso [bigskate_lencomp], [prepare_composition_data()], [plot_composition_andre()] +"bigskate_caal" diff --git a/R/plot_composition.R b/R/plot_composition.R new file mode 100644 index 00000000..1a1e600b --- /dev/null +++ b/R/plot_composition.R @@ -0,0 +1,705 @@ +#' Plot composition distributions +#' +#' Plot age or length compositions as distributions (proportion by bin), one +#' panel per year, optionally overlaying the model-expected values. Setting +#' `aggregate = TRUE` instead sums the compositions across years so that each +#' fleet is summarized in a single panel. +#' +#' @param dat A tibble returned from [convert_output()] that contains +#' composition data (see [bigskate_lencomp]), or composition data in any other +#' format accepted by [prepare_composition_data()]. +#' @param show_expected Logical. TRUE/FALSE; overlay the expected values as +#' lines. Ignored if `dat` contains no expected values. Default: TRUE +#' +#' @param aggregate Logical. TRUE/FALSE; aggregate compositions across years +#' within each fleet. Years are weighted by their sample size when sample +#' sizes are available. Default: FALSE +#' +#' @param ncol Number. Number of facet columns. NULL lets `ggplot2` choose. Default: NULL +#' +#' @param direction String. Order of the panels: "v" fills panels down each column +#' and then across, so consecutive years are stacked vertically and cohorts +#' are easier to follow from panel to panel; "h" fills panels across each row. +#' +#' Default: "v" +#' +#' @inheritParams prepare_composition_data +#' +#' @returns A `ggplot2` object. +#' +#' @details When compositions are sex-specific (both "female" and "male" +#' values in the `sex` column), females are plotted above zero and males +#' below zero. Bars show observed proportions and lines show expected +#' proportions. Females and males are colored with the two ends of +#' `viridis::viridis(2, option = "viridis", end = 0.75)` (`"#440154"` and +#' `"#5DC863"`); add `ggplot2::scale_fill_manual()` and +#' `ggplot2::scale_color_manual()` to the returned plot to use other colors. +#' +#' If more than one fleet is plotted without aggregating, panels are +#' created for every fleet and year combination; use `fleet` to focus on one +#' fleet at a time. +#' +#' @section Equivalent plots in other packages: +#' r4ss: `r4ss::SSplotComps()` subplot 1 (by year) and +#' subplot 21 (aggregated across years). +#' +#' @seealso [prepare_composition_data()], [plot_composition_bubble()], +#' [plot_composition_residuals()], [plot_composition_sexratio()], [plot_composition_francis()] +#' @export +#' +#' @examples +#' # Observations only +#' plot_composition_distribution(bigskate_lencomp, fleet = "WCGBTS", show_expected = FALSE) +#' +#' # Observed and expected +#' plot_composition_distribution(bigskate_lencomp, fleet = "Triennial") +#' +#' # All fleets aggregated across years +#' plot_composition_distribution(bigskate_lencomp, aggregate = TRUE) +plot_composition_distribution <- function( + dat, + show_expected = TRUE, + aggregate = FALSE, + comp_type = NULL, + fleet = NULL, + ncol = NULL, + direction = "v" +) { + comps <- prepare_composition_data(dat, comp_type = comp_type, fleet = fleet) + bin_label <- comp_bin_label(comps) + + if (aggregate) { + comps <- aggregate_comp_years(comps) + } + show_expected <- show_expected && any(!is.na(comps$expected)) + two_sex <- all(c("female", "male") %in% comps$sex) + + # Mirror males below zero for sex-specific comps + comps <- comps |> + dplyr::mutate( + direction = dplyr::if_else(two_sex & .data$sex == "male", -1, 1), + obs_plot = .data$observed * .data$direction, + exp_plot = .data$expected * .data$direction + ) + + bins <- sort(unique(comps$bin)) + width <- if (length(bins) > 1) min(diff(bins)) else 1 + + plt <- ggplot2::ggplot(comps, ggplot2::aes(x = .data$bin)) + if (length(unique(comps$sex)) > 1) { + plt <- plt + + ggplot2::geom_col( + ggplot2::aes(y = .data$obs_plot, fill = .data$sex), + width = width * 0.9, + alpha = 0.6, + position = if (two_sex) "identity" else "dodge" + ) + } else { + plt <- plt + + ggplot2::geom_col( + ggplot2::aes(y = .data$obs_plot), + width = width * 0.9, + alpha = 0.6, + fill = "gray50" + ) + } + if (show_expected) { + exp_dat <- dplyr::filter(comps, !is.na(.data$exp_plot)) + if (length(unique(comps$sex)) > 1) { + plt <- plt + + ggplot2::geom_line( + data = exp_dat, + ggplot2::aes(y = .data$exp_plot, group = .data$sex, color = .data$sex), + linewidth = 0.7 + ) + } else { + plt <- plt + + ggplot2::geom_line( + data = exp_dat, + ggplot2::aes(y = .data$exp_plot, group = .data$sex), + color = "#0085CA", + linewidth = 0.7 + ) + } + } + if (two_sex) { + plt <- plt + ggplot2::geom_hline(yintercept = 0, linewidth = 0.3) + } + + if (length(unique(comps$sex)) > 1) { + plt <- plt + + ggplot2::scale_fill_manual(values = comp_sex_colors(), aesthetics = c("fill", "colour")) + } + + ylab <- if (two_sex) "Proportion (females above, males below)" else "Proportion" + plt <- plt + + ggplot2::scale_y_continuous(labels = function(x) abs(x)) + + ggplot2::labs( + x = bin_label, + y = ylab, + fill = if (length(unique(comps$sex)) > 1) "Sex", + color = if (length(unique(comps$sex)) > 1 && show_expected) "Sex" + ) + + theme_noaa() + + # Facets + facet_vars <- if (aggregate) "fleet" else c("fleet", "year") + if (!aggregate && length(unique(comps$fleet)) == 1) facet_vars <- "year" + if (length(unique(comps$comp_type)) > 1) facet_vars <- c("comp_type", facet_vars) + if (!aggregate && length(unique(comps$fleet)) > 1) { + cli::cli_alert_info( + "Plotting {length(unique(comps$fleet))} fleets by year; use {.arg fleet} to plot one fleet at a time." + ) + } + plt <- plt + comp_facet(facet_vars, ncol = ncol, direction = direction) + if (!aggregate) { + plt <- plt + ggplot2::theme( + strip.text = ggplot2::element_text(size = 9), + axis.text = ggplot2::element_text(size = 8) + ) + } + plt +} + +#' Plot composition bubble plots +#' +#' Bubble plot of compositions by year (x-axis) and bin (y-axis). Bubbles can +#' show the observed (or expected) proportions, which makes strong and weak +#' cohorts easy to follow through time, or the Pearson residuals, which show +#' blocks or stripes of misfit among years or bins. +#' +#' @inheritParams plot_composition_distribution +#' @param value String. Value to plot. Options: "observed", "expected", or +#' "pearson". Default: "observed" +#' +#' @param max_size Number. Size of the largest bubble. Default: 6 +#' +#' @param conditional Logical. TRUE/FALSE; plot conditional age-at-length +#' data (see [prepare_composition_data()]), with age on the y-axis, length bin on +#' the x-axis, and one panel per year. Default: FALSE +#' +#' @returns A `ggplot2` object. +#' +#' @details Panels are created for each fleet, and for each sex when +#' compositions are sex-specific. For Pearson residuals, filled bubbles are +#' positive residuals (observed > expected) and open bubbles are negative +#' residuals (observed < expected), with bubble area proportional to the +#' absolute value of the residual. The maximum absolute residual for each +#' panel is included in the panel label. +#' +#' @section Equivalent plots in other packages: +#' r4ss: `r4ss::SSplotComps()` subplot 2 (bubble plot of +#' observations or residuals) and subplot 3 (conditional age-at-length). +#' +#' @seealso [prepare_composition_data()], [plot_composition_residuals()], [plot_composition_distribution()] +#' @export +#' +#' @examples +#' # Observed proportions +#' plot_composition_bubble(bigskate_lencomp) +#' +#' # Pearson residuals +#' plot_composition_bubble(bigskate_lencomp, value = "pearson") +#' +#' # Residuals for conditional age-at-length data +#' plot_composition_bubble( +#' dplyr::filter(bigskate_caal, year %in% 2016:2018), +#' value = "pearson", +#' fleet = "WCGBTS", +#' conditional = TRUE +#' ) +plot_composition_bubble <- function( + dat, + value = c("observed", "pearson", "expected"), + comp_type = NULL, + fleet = NULL, + max_size = 6, + ncol = NULL, + direction = "v", + conditional = FALSE +) { + value <- match.arg(value) + comps <- prepare_composition_data( + dat, + comp_type = if (conditional) "age" else comp_type, + fleet = fleet, + conditional = conditional + ) + bin_label <- comp_bin_label(comps) + # Conditional age-at-length: age by length bin, one panel per year + x_var <- if (conditional) "length_bin" else "year" + comps$value <- comps[[value]] + comps <- dplyr::filter(comps, !is.na(.data$value)) + if (nrow(comps) == 0) { + if (value == "pearson") { + cli::cli_abort(c( + "No Pearson residuals available.", + "i" = "Supply a {.field pearson} column, or {.field expected} values and sample sizes ({.field n}) so residuals can be calculated." + )) + } + cli::cli_abort("No {value} values found in {.arg dat}.") + } + + facet_vars <- c( + if (length(unique(comps$comp_type)) > 1) "comp_type", + if (!conditional || length(unique(comps$fleet)) > 1) "fleet", + if (length(unique(comps$sex)) > 1) "sex", + if (conditional) "year" + ) + if (value == "pearson") { + # Add the max absolute residual to a single panel label, ordered by + # comp type, fleet, and sex + comps <- comps |> + dplyr::arrange(.data$comp_type, .data$fleet, .data$sex, .data$year) |> + dplyr::group_by(dplyr::across(dplyr::all_of(facet_vars))) |> + dplyr::mutate( + panel = paste0( + paste(unlist(dplyr::cur_group()), collapse = ", "), + "\n(max = ", format(round(max(abs(.data$value)), 2), nsmall = 2), ")" + ) + ) |> + dplyr::ungroup() |> + dplyr::mutate(panel = factor(.data$panel, levels = unique(.data$panel))) |> + dplyr::mutate( + sign = factor( + dplyr::if_else(.data$value >= 0, "Observed > expected", "Observed < expected"), + levels = c("Observed > expected", "Observed < expected") + ) + ) + plt <- ggplot2::ggplot( + comps, + ggplot2::aes(x = .data[[x_var]], y = .data$bin, size = abs(.data$value), fill = .data$sign) + ) + + ggplot2::geom_point(shape = 21, color = "black", alpha = 0.8) + + ggplot2::scale_fill_manual( + values = c("Observed > expected" = "gray20", "Observed < expected" = "white"), + drop = FALSE + ) + + ggplot2::labs(size = "|Pearson residual|", fill = NULL) + + ggplot2::guides(fill = ggplot2::guide_legend(override.aes = list(size = 4))) + facet_vars <- "panel" + } else { + plt <- ggplot2::ggplot( + comps, + ggplot2::aes(x = .data[[x_var]], y = .data$bin, size = .data$value) + ) + + ggplot2::geom_point(shape = 21, color = "black", fill = "gray40", alpha = 0.5) + + ggplot2::labs(size = cap_first_letter(paste(value, "proportion"))) + } + + plt + + ggplot2::scale_size_area(max_size = max_size) + + ggplot2::scale_x_continuous( + breaks = if (conditional) ggplot2::waiver() else axis_breaks(comps$year), + guide = ggplot2::guide_axis(minor.ticks = TRUE) + ) + + ggplot2::labs(x = if (conditional) "Length bin" else "Year", y = bin_label) + + theme_noaa() + + comp_facet(facet_vars, ncol = ncol, direction = direction) +} + +#' Plot composition Pearson residuals +#' +#' A convenience wrapper around [plot_composition_bubble()] with +#' `value = "pearson"`. Residuals are taken from `dat` when supplied or +#' calculated by [prepare_composition_data()] assuming a multinomial distribution. +#' +#' @inheritParams plot_composition_bubble +#' @param ... Additional arguments passed to [plot_composition_bubble()]. +#' +#' @returns A `ggplot2` object. +#' @section Equivalent plots in other packages: +#' r4ss: `r4ss::SSplotComps()` subplot 2 for residuals by +#' fleet and subplot 24 (residuals compared across fleets). +#' +#' @seealso [plot_composition_bubble()], [prepare_composition_data()] +#' @export +#' +#' @examples +#' plot_composition_residuals(bigskate_lencomp) +#' +#' # Compare residuals across fleets for females +#' plot_composition_residuals( +#' dplyr::filter(bigskate_lencomp, sex == "female"), +#' ncol = 1 +#' ) +plot_composition_residuals <- function(dat, ...) { + plot_composition_bubble(dat, value = "pearson", ...) +} + +#' Plot sex ratios implied by composition data +#' +#' Plot the proportion female, female / (female + male), by bin for the +#' observed and expected compositions. Trends in the lack of fit to sex ratios +#' can be easier to see here than in residuals, especially for bins with +#' small proportions. +#' +#' @inheritParams plot_composition_distribution +#' @param interval Number. Coverage of the interval around the observed sex +#' ratio, calculated as a Jeffreys interval using the sample size in each bin +#' (the sample size multiplied by the proportion of the composition in the +#' bin). Set to NULL to omit intervals. Intervals are only shown when sample +#' sizes are available. Default: 0.75 +#' +#' @returns A `ggplot2` object. +#' +#' @details Requires sex-specific compositions with both "female" and +#' "male" values in the `sex` column of `dat`. Points show observed ratios +#' and lines show expected ratios. As in r4ss, the smallest observed +#' proportion in each composition (such as the small constant Stock Synthesis +#' adds to every bin) is subtracted from the observed and expected values +#' first, so bins with no fish observed are omitted. When `aggregate = TRUE`, female and male +#' compositions are summed across years (weighted by sample size when +#' available) before calculating ratios. +#' +#' @section Equivalent plots in other packages: +#' r4ss: `r4ss::SSplotSexRatio()` with +#' `sexratio.option = 2` (female:total). +#' +#' @seealso [prepare_composition_data()], [plot_composition_distribution()] +#' @export +#' +#' @examples +#' plot_composition_sexratio(bigskate_lencomp, fleet = "WCGBTS") +#' plot_composition_sexratio(bigskate_lencomp, aggregate = TRUE) +plot_composition_sexratio <- function( + dat, + aggregate = FALSE, + interval = 0.75, + comp_type = NULL, + fleet = NULL, + ncol = NULL, + direction = "v" +) { + comps <- prepare_composition_data(dat, comp_type = comp_type, fleet = fleet) + bin_label <- comp_bin_label(comps) + if (!all(c("female", "male") %in% comps$sex)) { + cli::cli_abort("Sex ratios require compositions with both {.val female} and {.val male} values in the {.field sex} column.") + } + + # Females and males side by side, weighted by sample size when available. + # As in r4ss, the smallest observation in each composition (e.g., the + # constant Stock Synthesis adds to every bin) is subtracted first, so that + # bins without fish do not appear as 50:50 sex ratios. + wide <- comps |> + dplyr::filter(.data$sex %in% c("female", "male")) |> + dplyr::group_by(.data$comp_type, .data$fleet, .data$year) |> + dplyr::mutate( + min_obs = min(.data$observed, na.rm = TRUE), + observed = pmax(.data$observed - .data$min_obs, 0), + expected = pmax(.data$expected - .data$min_obs, 0) + ) |> + dplyr::ungroup() |> + dplyr::mutate(w = dplyr::if_else(is.na(.data$n), 1, .data$n)) |> + dplyr::select("comp_type", "fleet", "year", "sex", "bin", "observed", "expected", "n", "w") |> + tidyr::pivot_wider(names_from = "sex", values_from = c("observed", "expected")) + group_vars <- if (aggregate) c("comp_type", "fleet", "bin") else c("comp_type", "fleet", "year", "bin") + + ratios <- wide |> + dplyr::group_by(dplyr::across(dplyr::all_of(group_vars))) |> + dplyr::summarise( + f_obs = sum(.data$observed_female * .data$w, na.rm = TRUE), + m_obs = sum(.data$observed_male * .data$w, na.rm = TRUE), + f_exp = sum(.data$expected_female * .data$w, na.rm = TRUE), + m_exp = sum(.data$expected_male * .data$w, na.rm = TRUE), + has_n = all(!is.na(.data$n)), + has_exp = any(!is.na(.data$expected_female)), + .groups = "drop" + ) |> + dplyr::mutate( + ratio_obs = .data$f_obs / (.data$f_obs + .data$m_obs), + ratio_exp = dplyr::if_else(.data$has_exp, .data$f_exp / (.data$f_exp + .data$m_exp), NA_real_), + # weighted sums equal the number of fish in the bin when w is the sample size + # (at least 1, as in r4ss) + n_bin = dplyr::if_else( + .data$has_n & .data$f_obs + .data$m_obs > 0, + pmax(.data$f_obs + .data$m_obs, 1), + NA_real_ + ) + ) + ratios$ratio_obs[!is.finite(ratios$ratio_obs)] <- NA_real_ + ratios$ratio_exp[!is.finite(ratios$ratio_exp)] <- NA_real_ + + show_interval <- !is.null(interval) && any(!is.na(ratios$n_bin)) + if (show_interval) { + ci <- jeffreys_interval(ratios$ratio_obs, ratios$n_bin, level = interval) + ratios$lower <- ci$lower + ratios$upper <- ci$upper + } + + plt <- ggplot2::ggplot(ratios, ggplot2::aes(x = .data$bin)) + + ggplot2::geom_hline(yintercept = 0.5, linetype = "dashed", color = "gray60") + + if (show_interval) { + plt <- plt + + ggplot2::geom_linerange( + data = dplyr::filter(ratios, !is.na(.data$ratio_obs), !is.na(.data$lower)), + ggplot2::aes(ymin = .data$lower, ymax = .data$upper), + color = "gray50" + ) + } + plt <- plt + + ggplot2::geom_point( + data = dplyr::filter(ratios, !is.na(.data$ratio_obs)), + ggplot2::aes(y = .data$ratio_obs, shape = "Observed"), + size = 1.5 + ) + if (any(!is.na(ratios$ratio_exp))) { + plt <- plt + + ggplot2::geom_line( + data = dplyr::filter(ratios, !is.na(.data$ratio_exp)), + ggplot2::aes(y = .data$ratio_exp, linetype = "Expected"), + color = "#0085CA", + linewidth = 0.8 + ) + } + + facet_vars <- if (aggregate) "fleet" else c("fleet", "year") + if (!aggregate && length(unique(ratios$fleet)) == 1) facet_vars <- "year" + if (length(unique(ratios$comp_type)) > 1) facet_vars <- c("comp_type", facet_vars) + + plt <- plt + + ggplot2::scale_y_continuous(limits = c(0, 1), breaks = c(0, 0.5, 1)) + + ggplot2::labs( + x = bin_label, + y = "Fraction female", + shape = NULL, + linetype = NULL, + caption = if (!is.null(interval) && any(!is.na(ratios$n_bin))) { + paste0("Vertical lines: ", round(interval * 100), "% Jeffreys intervals for observed ratios") + } + ) + + theme_noaa() + + comp_facet(facet_vars, ncol = ncol, direction = direction) + if (!aggregate) { + plt <- plt + ggplot2::theme( + strip.text = ggplot2::element_text(size = 9), + axis.text = ggplot2::element_text(size = 8) + ) + } + plt +} + +#' Plot mean age or length with Francis (method 8 in Table A1, TA1.8) data weights +#' +#' Time series of the mean age or length in the observed and expected +#' compositions. Developed as a diagnostic for the Francis (2011) data +#' weighting method (TA1.8), the plot is also a useful summary of trends in +#' compositions and lack of fit among years. +#' +#' @inheritParams plot_composition_distribution +#' @param interval Number. Coverage of the interval around the observed mean. Default: 0.95 +#' +#' @param show_francis Logical. TRUE/FALSE; add the Francis TA1.8 weight for +#' each fleet to the panel label and show thinner, capped intervals +#' representing sample sizes after applying that weight. Default: TRUE +#' +#' @returns A `ggplot2` object. +#' +#' @details For each year and fleet (with sexes combined), the observed mean +#' is \eqn{\bar{x}_o = \sum_b x_b o_b} and the expected mean is +#' \eqn{\bar{x}_e = \sum_b x_b e_b}, where \eqn{x_b} is the bin and \eqn{o_b} +#' and \eqn{e_b} are the observed and expected proportions. Assuming a +#' multinomial distribution, the standard error of the mean is +#' \eqn{\sqrt{v_e / N}}, where \eqn{v_e = \sum_b x_b^2 e_b - \bar{x}_e^2} is +#' the variance of the expected composition and \eqn{N} is the sample size. +#' Intervals are drawn around the observed means. +#' +#' The Francis weight is \eqn{1 / \mathrm{var}(r)} where +#' \eqn{r = (\bar{x}_o - \bar{x}_e) / SE} are the standardized residuals of +#' the mean across years. As in Stock Synthesis and +#' `r4ss::SSMethod.TA1.8()`, all partitions of a fleet (e.g., retained and +#' discarded) share one weight, and its 95% interval is calculated by +#' bootstrapping the standardized residuals (1000 resamples), so use +#' [set.seed()] for reproducible intervals. The weight assumes a multinomial likelihood and has not been +#' adapted for Dirichlet-multinomial or other likelihoods. +#' +#' Without expected values, only the observed means are plotted. Without +#' sample sizes, no intervals or weights are shown. +#' +#' @references Francis, R.I.C.C. (2011). Data weighting in statistical +#' fisheries stock assessment models. Canadian Journal of Fisheries and +#' Aquatic Sciences 68: 1124-1138. +#' +#' @section Equivalent plots in other packages: +#' r4ss: `r4ss::SSplotComps()` subplots 8 and 9, which +#' use `r4ss::SSMethod.TA1.8()`. +#' +#' @seealso [prepare_composition_data()], [plot_composition_distribution()] +#' @export +#' +#' @examples +#' plot_composition_francis(bigskate_lencomp) +plot_composition_francis <- function( + dat, + interval = 0.95, + show_francis = TRUE, + comp_type = NULL, + fleet = NULL, + ncol = NULL, + direction = "v" +) { + comps <- prepare_composition_data(dat, comp_type = comp_type, fleet = fleet) + bin_label <- comp_bin_label(comps) + means <- comp_mean_table(comps) + z <- stats::qnorm(1 - (1 - interval) / 2) + has_exp <- any(!is.na(means$mean_exp)) + has_se <- any(!is.na(means$se)) + + # Francis weights by fleet. As in Stock Synthesis and + # r4ss::SSMethod.TA1.8(), partitions of a fleet share one weight. + weights <- NULL + if (show_francis && has_exp && has_se) { + means$base_fleet <- base_fleet(means$fleet) + weights <- means |> + dplyr::filter(!is.na(.data$std_resid)) |> + dplyr::group_by(.data$comp_type, .data$base_fleet) |> + dplyr::filter(dplyr::n() > 1) |> + dplyr::summarise( + n_parts = length(unique(.data$fleet)), + weight = 1 / stats::var(.data$std_resid), + weight_ci = list(francis_boot_ci(.data$std_resid)), + .groups = "drop" + ) |> + dplyr::mutate( + weight_label = paste0( + "TA1.8 weight", dplyr::if_else(.data$n_parts > 1, " (all partitions)", ""), + " = ", signif(.data$weight, 3), " (", + signif(purrr::map_dbl(.data$weight_ci, 1), 3), "-", + signif(purrr::map_dbl(.data$weight_ci, 2), 3), ")" + ) + ) + means <- means |> + dplyr::left_join( + dplyr::select(weights, "comp_type", "base_fleet", "weight", "weight_label"), + by = c("comp_type", "base_fleet") + ) |> + dplyr::mutate( + fleet = dplyr::if_else( + is.na(.data$weight_label), + .data$fleet, + paste0(.data$fleet, "\n", .data$weight_label) + ), + se_adj = .data$se / sqrt(.data$weight) + ) + } + + plt <- ggplot2::ggplot(means, ggplot2::aes(x = .data$year)) + if (has_se) { + plt <- plt + + ggplot2::geom_linerange( + ggplot2::aes( + ymin = .data$mean_obs - z * .data$se, + ymax = .data$mean_obs + z * .data$se + ), + color = "gray40", + linewidth = 0.8 + ) + if (!is.null(weights) && nrow(weights) > 0) { + plt <- plt + + ggplot2::geom_errorbar( + ggplot2::aes( + ymin = .data$mean_obs - z * .data$se_adj, + ymax = .data$mean_obs + z * .data$se_adj + ), + color = "gray20", + linewidth = 0.3, + width = 0.4 + ) + } + } + plt <- plt + + ggplot2::geom_point(ggplot2::aes(y = .data$mean_obs, shape = "Observed"), size = 2) + if (has_exp) { + plt <- plt + + ggplot2::geom_line( + data = dplyr::filter(means, !is.na(.data$mean_exp)), + ggplot2::aes(y = .data$mean_exp, linetype = "Expected"), + color = "#0085CA", + linewidth = 0.8 + ) + } + + caption <- NULL + if (has_se) { + caption <- paste0(round(interval * 100), "% intervals based on current sample sizes") + if (!is.null(weights) && nrow(weights) > 0) { + caption <- paste0( + caption, + "; thinner intervals (with capped ends) show result of further adjusting sample sizes (Francis TA1.8)" + ) + } + } + + facet_vars <- c(if (length(unique(means$comp_type)) > 1) "comp_type", "fleet") + plt + + ggplot2::scale_x_continuous( + breaks = axis_breaks(means$year), + guide = ggplot2::guide_axis(minor.ticks = TRUE) + ) + + ggplot2::labs( + x = "Year", + y = paste("Mean", tolower(bin_label)), + shape = NULL, + linetype = NULL, + caption = caption + ) + + theme_noaa() + + comp_facet(facet_vars, ncol = ncol, direction = direction, scales = "free_y") +} + +# Bootstrap 95% interval for the Francis weight, following +# r4ss::SSMethod.TA1.8() (1000 resamples of the standardized residuals) +francis_boot_ci <- function(std_resid, n_boot = 1000) { + boot <- matrix( + sample(std_resid, n_boot * length(std_resid), replace = TRUE), + nrow = length(std_resid) + ) + w <- apply(boot, 2, function(x) 1 / stats::var(x)) + as.vector(stats::quantile(w, c(0.025, 0.975), na.rm = TRUE)) +} + +# Mean bin of observed and expected compositions by year and fleet. As in +# r4ss::SSMethod.TA1.8(), sexes and partitions of a fleet in the same year +# are combined into one composition, with the mean sample size. +comp_mean_table <- function(comps) { + comps <- comps |> + dplyr::mutate( + part_name = fleet_partition(.data$fleet), + fleet = base_fleet(.data$fleet) + ) + comps |> + dplyr::group_by(.data$comp_type, .data$fleet) |> + dplyr::mutate( + fleet = if (length(unique(stats::na.omit(.data$part_name))) > 1) { + paste0(.data$fleet, " (", paste(sort(unique(stats::na.omit(.data$part_name))), collapse = " & "), ")") + } else { + .data$fleet + } + ) |> + dplyr::group_by(.data$comp_type, .data$fleet, .data$year, .data$bin) |> + dplyr::summarise( + observed = sum(.data$observed, na.rm = TRUE), + expected = if (all(is.na(.data$expected))) NA_real_ else sum(.data$expected, na.rm = TRUE), + n = mean(.data$n), + .groups = "drop" + ) |> + dplyr::group_by(.data$comp_type, .data$fleet, .data$year) |> + dplyr::summarise( + mean_obs = sum(.data$bin * .data$observed) / sum(.data$observed), + mean_exp = sum(.data$bin * .data$expected) / sum(.data$expected), + # Use the expected distribution for the variance when available + var_bin = if (all(is.na(.data$expected))) { + sum(.data$bin^2 * .data$observed) / sum(.data$observed) - .data$mean_obs[1]^2 + } else { + sum(.data$bin^2 * .data$expected) / sum(.data$expected) - .data$mean_exp[1]^2 + }, + n = mean(.data$n), + .groups = "drop" + ) |> + dplyr::filter(is.finite(.data$mean_obs)) |> + dplyr::mutate( + se = sqrt(.data$var_bin / .data$n), + std_resid = (.data$mean_obs - .data$mean_exp) / .data$se + ) +} diff --git a/R/plot_composition_diagnostics.R b/R/plot_composition_diagnostics.R new file mode 100644 index 00000000..4fc994c9 --- /dev/null +++ b/R/plot_composition_diagnostics.R @@ -0,0 +1,523 @@ +#' Plot input versus effective sample size of compositions +#' +#' Compare the input (or adjusted input) sample size of each composition with +#' its effective sample size, the sample size implied by how well the model +#' fits the composition. This is the diagnostic behind the McAllister and +#' Ianelli (1997) data weighting method. +#' +#' @inheritParams plot_composition_distribution +#' @param dat A tibble returned from [convert_output()] that contains +#' composition data (see [bigskate_lencomp]), or composition data in any other +#' format accepted by [prepare_composition_data()]. For Stock Synthesis +#' output converted from a Report.sso file without compositions by bin, the +#' adjusted input (`nsamp_adj`) and effective (`effn`) sample sizes in the +#' `FIT_LEN_COMPS` and `FIT_AGE_COMPS` tables are used instead (as in +#' `stockplotr::example_data`). +#' @param show_years Logical. TRUE/FALSE; label each point with its year. Default: FALSE +#' +#' @returns A `ggplot2` object. +#' +#' @details For each composition (year and fleet), the effective sample size +#' is +#' \deqn{N_{eff} = \sum_b e_b (1 - e_b) / \sum_b (o_b - e_b)^2} +#' where \eqn{o_b} and \eqn{e_b} are the observed and expected proportions in +#' bin \eqn{b} (McAllister and Ianelli 1997), the same calculation used by +#' Stock Synthesis. +#' +#' Each panel shows a 1:1 line, a loess smoother (dashed red, when there are +#' more than six distinct input sample sizes), the arithmetic mean of the +#' input sample sizes (vertical dashed line), and the harmonic mean of the +#' effective sample sizes (horizontal dashed line). The ratio of the harmonic +#' mean effective sample size to the mean input sample size, the +#' McAllister-Ianelli weight, is included in each panel label. Ratios below +#' one suggest that input sample sizes are too large relative to the fit. As +#' in Stock Synthesis, the weight is calculated across all partitions of a +#' fleet; multiplying it by the current variance adjustment gives the +#' `Recommend_var_adj` reported by Stock Synthesis. +#' +#' Effective sample sizes from this formula are not appropriate for +#' compositions fit with a Dirichlet-multinomial likelihood. +#' +#' @references McAllister, M.K., and Ianelli, J.N. (1997). Bayesian stock +#' assessment using catch-age data and the sampling-importance resampling +#' algorithm. Canadian Journal of Fisheries and Aquatic Sciences 54: 284-300. +#' +#' @section Equivalent plots in other packages: +#' r4ss: `r4ss::SSplotComps()` subplot 7 ("N-EffN +#' comparison"). +#' +#' @seealso [prepare_composition_data()], [plot_composition_francis()] +#' @export +#' +#' @examples +#' plot_composition_samples(bigskate_lencomp) +#' +#' # Stock Synthesis output converted with convert_output() +#' plot_composition_samples(stockplotr::example_data, comp_type = "length") +plot_composition_samples <- function( + dat, + comp_type = NULL, + fleet = NULL, + show_years = FALSE, + ncol = NULL, + direction = "v" +) { + # Older converted Stock Synthesis output has only the per-composition fit + # tables, not compositions by bin + if (is_ss3_fit_summary(dat) && !has_bin_comps(dat)) { + if (!is.null(comp_type)) comp_type <- match.arg(comp_type, c("age", "length")) + sizes <- comp_sample_size_from_ss3(dat, comp_type = comp_type) + if (!is.null(fleet)) { + sizes <- sizes[sizes$fleet %in% as.character(fleet), , drop = FALSE] + } + if (nrow(sizes) == 0) { + cli::cli_abort("No sample sizes remain after filtering by {.arg comp_type} and {.arg fleet}.") + } + } else { + comps <- prepare_composition_data(dat, comp_type = comp_type, fleet = fleet) + sizes <- comp_effn_table(comps) + } + sizes <- dplyr::filter(sizes, is.finite(.data$n), is.finite(.data$effn)) + if (nrow(sizes) == 0) { + cli::cli_abort(c( + "Effective sample sizes could not be calculated.", + "i" = "Supply {.field expected} values and sample sizes ({.field n})." + )) + } + + facet_vars <- c(if (length(unique(sizes$comp_type)) > 1) "comp_type", "fleet") + # Harmonic mean effective N and mean input N by panel, and the + # McAllister-Ianelli weight by fleet. As in Stock Synthesis, partitions of + # a fleet share one weight. + sizes$base_fleet <- base_fleet(sizes$fleet) + mi <- sizes |> + dplyr::group_by(.data$comp_type, .data$base_fleet) |> + dplyr::summarise( + n_parts = length(unique(.data$fleet)), + mi_weight = (1 / mean(1 / .data$effn)) / mean(.data$n), + .groups = "drop" + ) + summ <- sizes |> + dplyr::group_by(dplyr::across(dplyr::all_of(unique(c("comp_type", facet_vars, "base_fleet"))))) |> + dplyr::summarise( + mean_n = mean(.data$n), + hmean_effn = 1 / mean(1 / .data$effn), + .groups = "drop" + ) |> + dplyr::left_join(mi, by = c("comp_type", "base_fleet")) |> + dplyr::mutate( + panel = paste0( + .data$fleet, "\nMcAllister-Ianelli weight", + dplyr::if_else(.data$n_parts > 1, " (all partitions)", ""), + " = ", signif(.data$mi_weight, 3) + ) + ) + sizes <- dplyr::left_join(sizes, summ, by = unique(c("comp_type", facet_vars, "base_fleet"))) + + # Loess smoother by panel when there are enough distinct sample sizes + smooth <- lapply(split(sizes, sizes$panel), function(d) { + if (length(unique(d$n)) <= 6 || diff(range(d$n)) <= 2) { + return(NULL) + } + fit <- suppressWarnings(stats::loess(effn ~ n, data = d, degree = 1)) + d <- d[order(d$n), ] + d$fit <- stats::predict(fit, newdata = data.frame(n = d$n)) + d + }) + smooth <- dplyr::bind_rows(smooth) + + plt <- ggplot2::ggplot(sizes, ggplot2::aes(x = .data$n, y = .data$effn)) + + ggplot2::geom_abline(slope = 1, intercept = 0, color = "black") + + ggplot2::geom_vline( + data = summ, + ggplot2::aes(xintercept = .data$mean_n), + linetype = "22", + color = "#0085CA" + ) + + ggplot2::geom_hline( + data = summ, + ggplot2::aes(yintercept = .data$hmean_effn), + linetype = "22", + color = "#0085CA" + ) + + ggplot2::geom_point(color = "gray30", size = 2) + if (nrow(smooth) > 0) { + plt <- plt + + ggplot2::geom_line( + data = smooth, + ggplot2::aes(x = .data$n, y = .data$fit), + color = "#D55E00", + linetype = "dashed" + ) + } + if (show_years) { + plt <- plt + + ggplot2::geom_text( + ggplot2::aes(label = .data$year), + hjust = -0.2, + size = 2.8 + ) + } + plt + + ggplot2::expand_limits(x = 0, y = 0) + + ggplot2::labs( + x = "Adjusted input sample size (N adj.)", + y = "Effective sample size (N eff.)", + caption = "Solid line: 1:1. Dashed blue lines: arithmetic mean N adj. and harmonic mean N eff." + ) + + theme_noaa() + + comp_facet(c(if ("comp_type" %in% facet_vars) "comp_type", "panel"), ncol = ncol, direction = direction, scales = "free") +} + +#' Plot the distribution of composition residuals +#' +#' Quantile-quantile plot or histogram of Pearson residuals for each fleet, +#' to check for heavy tails, skewness, and outliers. +#' +#' @inheritParams plot_composition_distribution +#' @param type String. Type of plot. Options: "qq" (normal quantile-quantile +#' plot) or "histogram" (with a standard normal density overlaid). Default: "qq" +#' +#' @returns A `ggplot2` object. +#' +#' @details The standard deviation of the normalized residuals (SDNR) for +#' each fleet is included in the panel label. Values well above one suggest +#' that the compositions are given more weight (larger sample sizes) than the +#' fit supports. Pearson residuals for compositions are not expected to be +#' exactly normally distributed, especially for bins with small expected +#' proportions or small sample sizes, so treat departures in the tails with +#' some caution. +#' +#' @seealso [plot_composition_residuals()], [plot_composition_residual_pattern()], +#' [prepare_composition_data()] +#' @export +#' +#' @examples +#' plot_composition_residual_distribution(bigskate_lencomp) +#' plot_composition_residual_distribution(bigskate_lencomp, type = "histogram") +plot_composition_residual_distribution <- function( + dat, + type = c("qq", "histogram"), + comp_type = NULL, + fleet = NULL, + ncol = NULL, + direction = "v" +) { + type <- match.arg(type) + comps <- comp_residuals_or_abort(dat, comp_type = comp_type, fleet = fleet) + facet_vars <- c(if (length(unique(comps$comp_type)) > 1) "comp_type", "fleet") + comps <- comps |> + dplyr::group_by(dplyr::across(dplyr::all_of(facet_vars))) |> + dplyr::mutate( + panel = paste0(.data$fleet, " (SDNR = ", format(round(stats::sd(.data$pearson), 2), nsmall = 2), ")") + ) |> + dplyr::ungroup() + + if (type == "qq") { + plt <- ggplot2::ggplot(comps, ggplot2::aes(sample = .data$pearson)) + + ggplot2::geom_qq_line(color = "#0085CA", linewidth = 0.8) + + ggplot2::geom_qq(size = 1, alpha = 0.5) + + ggplot2::labs(x = "Theoretical quantiles (standard normal)", y = "Pearson residual") + } else { + plt <- ggplot2::ggplot(comps, ggplot2::aes(x = .data$pearson)) + + ggplot2::geom_histogram( + ggplot2::aes(y = ggplot2::after_stat(.data$density)), + bins = 30, + fill = "gray60", + color = "white" + ) + + ggplot2::stat_function(fun = stats::dnorm, color = "#0085CA", linewidth = 0.8) + + ggplot2::labs(x = "Pearson residual", y = "Density") + } + plt + + theme_noaa() + + comp_facet(c(if ("comp_type" %in% facet_vars) "comp_type", "panel"), ncol = ncol, direction = direction, scales = "free") +} + +#' Plot patterns in composition residuals by bin or year +#' +#' Plot the mean Pearson residual for each bin (across years) or each year +#' (across bins), with a runs test for non-random patterns in the signs of +#' the residuals. These plots summarize the stripes that can appear in +#' residual bubble plots, for example, consistently negative residuals at +#' older ages caused by mis-specified selectivity. +#' +#' @inheritParams plot_composition_distribution +#' @param by String. Summarize residuals by "bin" (age or length) or by +#' "year". Default: "bin" +#' +#' @returns A `ggplot2` object. +#' +#' @details Vertical lines are plus and minus two standard errors of the mean +#' residual. The runs test (Wald-Wolfowitz) is applied to the signs of the +#' residuals ordered by year (when `by = "bin"`) or by bin (when +#' `by = "year"`). Filled points have fewer runs than expected by chance +#' (one-sided p < 0.05), which indicates a non-random pattern of residuals. +#' When all residuals have the same sign, the p-value is the probability of +#' that happening by chance, \eqn{2 \times 0.5^n}. +#' Residuals are summarized separately for each fleet and sex. +#' +#' @seealso [plot_composition_residuals()], [plot_composition_residual_distribution()], +#' [prepare_composition_data()] +#' @export +#' +#' @examples +#' plot_composition_residual_pattern(bigskate_lencomp) +#' plot_composition_residual_pattern(bigskate_lencomp, by = "year") +plot_composition_residual_pattern <- function( + dat, + by = c("bin", "year"), + comp_type = NULL, + fleet = NULL, + ncol = NULL, + direction = "v" +) { + by <- match.arg(by) + comps <- comp_residuals_or_abort(dat, comp_type = comp_type, fleet = fleet) + bin_label <- comp_bin_label(comps) + order_var <- if (by == "bin") "year" else "bin" + + summ <- comps |> + dplyr::arrange(.data[[order_var]]) |> + dplyr::group_by(.data$comp_type, .data$fleet, .data$sex, .data[[by]]) |> + dplyr::summarise( + mean_resid = mean(.data$pearson), + se = stats::sd(.data$pearson) / sqrt(dplyr::n()), + runs_p = runs_test(.data$pearson), + .groups = "drop" + ) |> + dplyr::mutate( + runs = factor( + dplyr::if_else(!is.na(.data$runs_p) & .data$runs_p < 0.05, "Non-random (p < 0.05)", "Random"), + levels = c("Non-random (p < 0.05)", "Random") + ) + ) + + facet_vars <- c( + if (length(unique(summ$comp_type)) > 1) "comp_type", + "fleet", + if (length(unique(summ$sex)) > 1) "sex" + ) + + ggplot2::ggplot(summ, ggplot2::aes(x = .data[[by]], y = .data$mean_resid)) + + ggplot2::geom_hline(yintercept = 0, color = "gray50") + + ggplot2::geom_linerange( + ggplot2::aes( + ymin = .data$mean_resid - 2 * .data$se, + ymax = .data$mean_resid + 2 * .data$se + ), + color = "gray40", + na.rm = TRUE + ) + + ggplot2::geom_point(ggplot2::aes(fill = .data$runs), shape = 21, size = 2.5) + + ggplot2::scale_fill_manual( + values = c("Non-random (p < 0.05)" = "gray10", "Random" = "white"), + drop = FALSE + ) + + ggplot2::labs( + x = if (by == "bin") bin_label else "Year", + y = "Mean Pearson residual", + fill = paste("Runs test across", if (by == "bin") "years" else "bins") + ) + + theme_noaa() + + comp_facet(facet_vars, ncol = ncol, direction = direction) +} + +#' Plot Andre Punt's mean age at length for conditional age-at-length data +#' +#' For conditional age-at-length data, plot the observed and expected mean +#' age in each length bin, and the standard error of the mean age, for each +#' year. These are "Andre's plots" from `r4ss::SSplotComps()` (subplot 12). +#' +#' @inheritParams plot_composition_distribution +#' @param dat A tibble returned from [convert_output()] that contains +#' conditional age-at-length data (see [bigskate_caal]), or conditional data +#' in any other format accepted by [prepare_composition_data()] with +#' `conditional = TRUE`. +#' @param interval Number. Coverage of the intervals around the observed +#' values. Default: 0.90, corresponding to 90% intervals +#' +#' @param flip Logical. TRUE/FALSE; put age (the mean age or its standard +#' error) on the horizontal axis and length on the vertical axis, as in a +#' growth curve. The default (FALSE) puts length on the horizontal axis, +#' as in r4ss. +#' +#' @returns A `ggplot2` object with one row of panels per year (and per sex +#' and fleet when there is more than one): mean age at length on the left and +#' standard error of the mean age on the right. +#' +#' @details Within each year and length bin, the observed and expected mean +#' ages are \eqn{\sum_a a\, o_a} and \eqn{\sum_a a\, e_a}, and the standard +#' errors are the corresponding standard deviations of age divided by +#' \eqn{\sqrt{N}}, where \eqn{N} is the number of fish aged in the length +#' bin. Following r4ss, the interval for the mean is the observed mean plus or +#' minus the normal quantile times the observed standard error, and the +#' interval for the standard error is based on the chi-square distribution. +#' Observed values outside the intervals, or expected values that +#' consistently differ from the observed values, suggest mis-specified growth +#' or variability in length at age. +#' +#' The statistics are calculated separately for each sex. Unlike r4ss, which +#' combines sexes, this avoids jumps in the expected values when the ratio of +#' sample sizes varies between sexes. Length bins with no observations or no +#' sample size are omitted. Plot one fleet at a time with `fleet`, and subset +#' years by filtering `dat`. +#' +#' @section Equivalent plots in other packages: +#' r4ss: `r4ss::SSplotComps()` subplot 12 (Andre's plots of +#' mean age and standard deviation for conditional age-at-length data). +#' +#' @seealso [prepare_composition_data()], [plot_composition_bubble()] +#' @export +#' +#' @examples +#' plot_composition_andre( +#' dplyr::filter(bigskate_caal, year %in% 2016:2018), +#' fleet = "WCGBTS" +#' ) +#' +#' # Age on the horizontal axis +#' plot_composition_andre( +#' dplyr::filter(bigskate_caal, year %in% 2016:2018), +#' fleet = "WCGBTS", +#' flip = TRUE +#' ) +plot_composition_andre <- function( + dat, + interval = 0.90, + flip = FALSE, + fleet = NULL, + ncol = NULL +) { + comps <- prepare_composition_data(dat, comp_type = "age", fleet = fleet, conditional = TRUE) + if (length(unique(comps$fleet)) > 1) { + cli::cli_alert_info( + "Plotting {length(unique(comps$fleet))} fleets; use {.arg fleet} to plot one fleet at a time." + ) + } + z <- stats::qnorm(1 - (1 - interval) / 2) + stats_df <- comps |> + dplyr::group_by(.data$fleet, .data$year, .data$sex, .data$length_bin) |> + dplyr::filter( + max(.data$observed, na.rm = TRUE) > 1e-4, + !is.na(dplyr::first(.data$n)), + dplyr::first(.data$n) > 0 + ) |> + dplyr::summarise( + N = dplyr::first(.data$n), + obs_mean = sum(.data$bin * .data$observed) / sum(.data$observed), + obs_sd = sqrt(max(0, sum(.data$bin^2 * .data$observed) / sum(.data$observed) - .data$obs_mean[1]^2)), + exp_mean = sum(.data$bin * .data$expected) / sum(.data$expected), + exp_sd = sqrt(max(0, sum(.data$bin^2 * .data$expected) / sum(.data$expected) - .data$exp_mean[1]^2)), + .groups = "drop" + ) |> + dplyr::mutate( + obs_se = .data$obs_sd / sqrt(.data$N), + exp_se = .data$exp_sd / sqrt(.data$N) + ) + if (nrow(stats_df) == 0) { + cli::cli_abort("No conditional age-at-length data with observations and sample sizes found.") + } + + mean_df <- stats_df |> + dplyr::transmute( + .data$fleet, .data$year, .data$sex, .data$length_bin, + statistic = "Mean age", + observed = .data$obs_mean, + expected = .data$exp_mean, + lower = .data$obs_mean - z * .data$obs_se, + upper = .data$obs_mean + z * .data$obs_se + ) + se_df <- stats_df |> + dplyr::transmute( + .data$fleet, .data$year, .data$sex, .data$length_bin, + statistic = "SE of mean age", + observed = .data$obs_se, + expected = .data$exp_se, + lower = dplyr::if_else( + .data$N > 1, + .data$obs_se * sqrt(pmax(.data$N - 1, 0) / stats::qchisq(1 - (1 - interval) / 2, .data$N)), + NA_real_ + ), + upper = dplyr::if_else( + .data$N > 1, + .data$obs_se * sqrt(pmax(.data$N - 1, 0) / stats::qchisq((1 - interval) / 2, .data$N)), + NA_real_ + ) + ) + plot_df <- dplyr::bind_rows(mean_df, se_df) |> + dplyr::mutate(statistic = factor(.data$statistic, levels = c("Mean age", "SE of mean age"))) |> + dplyr::arrange(.data$fleet, .data$sex, .data$year, .data$statistic, .data$length_bin) + + row_vars <- c( + if (length(unique(plot_df$fleet)) > 1) "fleet", + if (length(unique(plot_df$sex)) > 1) "sex", + "year" + ) + + caption <- paste0( + "Left: mean age at length (obs. and exp.) with ", round(interval * 100), + "% intervals based on adding ", round(z, 2), " SE of mean to the data.\n", + "Right: SE of mean age at length (obs. and exp.) with ", round(interval * 100), + "% intervals based on the chi-square distribution." + ) + + if (flip) { + # Age (mean or SE) on the horizontal axis and length on the vertical axis; + # intervals and lines run along the length axis + plt <- ggplot2::ggplot(plot_df, ggplot2::aes(y = .data$length_bin)) + + ggplot2::geom_ribbon( + ggplot2::aes(xmin = .data$lower, xmax = .data$upper), + fill = "gray90", + orientation = "y", + na.rm = TRUE + ) + + ggplot2::geom_path(ggplot2::aes(x = .data$lower), linetype = "dotted", na.rm = TRUE) + + ggplot2::geom_path(ggplot2::aes(x = .data$upper), linetype = "dotted", na.rm = TRUE) + + ggplot2::geom_path( + ggplot2::aes(x = .data$expected, linetype = "Expected"), + color = "#0085CA", + linewidth = 0.8, + na.rm = TRUE + ) + + ggplot2::geom_point(ggplot2::aes(x = .data$observed, shape = "Observed"), size = 1.5) + + ggplot2::expand_limits(x = 0) + + ggplot2::labs(x = NULL, y = "Length bin") + free <- "free_x" + } else { + plt <- ggplot2::ggplot(plot_df, ggplot2::aes(x = .data$length_bin)) + + ggplot2::geom_ribbon( + ggplot2::aes(ymin = .data$lower, ymax = .data$upper), + fill = "gray90", + na.rm = TRUE + ) + + ggplot2::geom_line(ggplot2::aes(y = .data$lower), linetype = "dotted", na.rm = TRUE) + + ggplot2::geom_line(ggplot2::aes(y = .data$upper), linetype = "dotted", na.rm = TRUE) + + ggplot2::geom_line( + ggplot2::aes(y = .data$expected, linetype = "Expected"), + color = "#0085CA", + linewidth = 0.8, + na.rm = TRUE + ) + + ggplot2::geom_point(ggplot2::aes(y = .data$observed, shape = "Observed"), size = 1.5) + + ggplot2::expand_limits(y = 0) + + ggplot2::labs(x = "Length bin", y = NULL) + free <- "free_y" + } + + plt + + ggplot2::facet_grid( + rows = ggplot2::vars(!!!rlang::syms(row_vars)), + cols = ggplot2::vars(.data$statistic), + scales = free, + labeller = ggplot2::labeller(.multi_line = FALSE) + ) + + ggplot2::labs(shape = NULL, linetype = NULL, caption = caption) + + theme_noaa() + + ggplot2::theme( + strip.text = ggplot2::element_text(size = 9), + axis.text = ggplot2::element_text(size = 8), + plot.caption = ggplot2::element_text(hjust = 0, size = 9) + ) +} + diff --git a/R/utils_composition.R b/R/utils_composition.R new file mode 100644 index 00000000..d4bc4cd7 --- /dev/null +++ b/R/utils_composition.R @@ -0,0 +1,785 @@ +#' Prepare composition data for plotting +#' +#' Reshape age or length composition data (observed and, optionally, +#' expected proportions) from [convert_output()] into the tidy format used by +#' the composition plotting functions ([plot_composition_distribution()], +#' [plot_composition_bubble()], [plot_composition_residuals()], +#' [plot_composition_sexratio()], [plot_composition_francis()], +#' [plot_composition_samples()], [plot_composition_residual_distribution()], +#' [plot_composition_residual_pattern()], and [plot_composition_andre()]). +#' Each of these functions calls `prepare_composition_data()` itself, so it +#' only needs to be called directly to inspect or modify the data. +#' +#' @param dat A tibble returned from [convert_output()] (see +#' [bigskate_lencomp]). Compositions are the rows whose `label` contains +#' "composition", for example: +#' \describe{ +#' \item{Stock Synthesis (from an `r4ss::SS_output()` list)}{ +#' "length_composition_observed", "length_composition_predicted", +#' "length_composition_pearson_residual", and +#' "length_composition_sample_size" (and the "age_" equivalents), with +#' conditional age-at-length data labeled "conditional_age_composition_*".} +#' \item{FIMS}{"age_composition_proportion" and "age_composition_expected" +#' (and the "length_" equivalents).} +#' \item{BAM and Rceattle}{"age_composition_observed" and +#' "age_composition_predicted", or "composition_observed" and +#' "composition_predicted".} +#' } +#' Bins are taken from the `age` or `length_bins` column, and partitions from +#' the `part` column. Other formats are also recognized: +#' \describe{ +#' \item{Tidy composition data}{Columns `year`, `bin`, and `observed`, with +#' optional columns `fleet`, `sex`, `part` (partition: 0 = whole catch, +#' 1 = discard, 2 = retained), `expected`, `n` (input or adjusted sample +#' size), `pearson`, `comp_type`, and `length_bin` (for conditional +#' age-at-length data).} +#' \item{FIMS estimates}{Output from `FIMS::get_estimates()` that contains +#' `observed` and `expected` columns. Rows whose `label` contains "comp" are +#' kept and the bin is taken from the `age` or `length` column.} +#' \item{r4ss output}{A list returned by `r4ss::SS_output()` (length and +#' age compositions are combined and fleet names are added) or its +#' `lendbase` or `agedbase` elements (columns `Yr`, `Fleet`, `Sex`, `Bin`, +#' `Obs`, `Exp`, `Pearson`, `Nsamp_adj`, ...). Conditional +#' age-at-length rows are dropped from `agedbase` so that only marginal age +#' compositions remain, unless `conditional = TRUE`.} +#' } +#' For conditional age-at-length data (`conditional = TRUE`), see +#' [bigskate_caal]. +#' @param comp_type String. Type of composition to keep when `dat` contains +#' more than one. Options: "age", "length", or NULL (keep all). Default: NULL +#' +#' @param fleet Character or numeric vector. Fleet(s) to keep, matching +#' either the fleet name (e.g., "Fishery_current") or the fleet name with its +#' partition (e.g., "Fishery_current (retained)"). NULL keeps all fleets. Default: NULL +#' +#' @param normalize Logical. TRUE/FALSE; rescale observed and expected values +#' so that they sum to one within each year, fleet, and composition type +#' (and length bin for conditional data). Sex-specific compositions are +#' normalized jointly across sexes, unless each sex already sums to one on +#' its own (single-sex compositions). Use TRUE when `dat` contains counts +#' rather than proportions. Default: TRUE +#' +#' @param conditional Logical. TRUE/FALSE; keep conditional age-at-length +#' data instead of marginal compositions. Conditional compositions are +#' normalized within each length bin. Used by [plot_composition_andre()] and +#' [plot_composition_bubble()]. Default: FALSE +#' +#' @returns A tibble with one row per year, fleet, sex, and bin and columns +#' `comp_type`, `fleet`, `year`, `sex`, `bin`, `observed`, `expected`, `n`, +#' and `pearson` (plus `length_bin` when `conditional = TRUE`). Columns that +#' cannot be determined from `dat` are `NA`. +#' +#' @details When Pearson residuals are not supplied, they are calculated +#' assuming a multinomial distribution as +#' \deqn{r = (o - e) / \sqrt{e (1 - e) / N}} +#' where \eqn{o} and \eqn{e} are the observed and expected proportions and +#' \eqn{N} is the sample size in the `n` column. If `n` is not available, +#' residuals are left as `NA`. +#' +#' @seealso [plot_composition_distribution()], [plot_composition_bubble()], [plot_composition_residuals()], +#' [plot_composition_sexratio()], [plot_composition_francis()] +#' @export +#' +#' @examples +#' comps <- prepare_composition_data(bigskate_lencomp, fleet = "WCGBTS") +#' head(comps) +#' +#' # Conditional age-at-length data +#' prepare_composition_data(bigskate_caal, conditional = TRUE) +#' +#' # Data in the stockplotr long format are reshaped automatically +#' long <- data.frame( +#' label = rep(c("age_composition_proportion", "age_composition_expected"), each = 3), +#' year = 2020, +#' fleet = "fleet1", +#' age = rep(1:3, 2), +#' estimate = c(0.2, 0.5, 0.3, 0.25, 0.45, 0.3) +#' ) +#' prepare_composition_data(long) +prepare_composition_data <- function( + dat, + comp_type = NULL, + fleet = NULL, + normalize = TRUE, + conditional = FALSE +) { + # Full r4ss::SS_output() list: combine length and marginal age comps, or + # use the conditional age-at-length data + if (!is.data.frame(dat) && is.list(dat) && + any(c("lendbase", "agedbase", "condbase") %in% names(dat))) { + dbase <- if (conditional) { + dat[["condbase"]] + } else { + dplyr::bind_rows(dat[["lendbase"]], dat[["agedbase"]]) + } + if (is.null(dbase) || nrow(dbase) == 0) { + cli::cli_abort("No {if (conditional) 'conditional age-at-length' else 'composition'} data found in the r4ss output.") + } + if (!is.null(dat[["FleetNames"]]) && nrow(dbase) > 0) { + dbase$Fleet_Name <- dat[["FleetNames"]][dbase$Fleet] + } + dat <- dbase + } + if (!is.data.frame(dat)) { + cli::cli_abort("{.arg dat} must be a data frame or an {.code r4ss::SS_output()} list.") + } + # Return early if the data are already prepared + prepared <- inherits(dat, "stockplotr_comp") + if (prepared) { + out <- dat + if (conditional != ("length_bin" %in% names(out))) { + cli::cli_abort("{.arg dat} was prepared with {.code conditional = {!conditional}}.") + } + } else { + out <- switch(detect_comp_format(dat), + "r4ss" = comp_from_r4ss(dat, conditional = conditional), + "obs_exp_cols" = comp_from_obs_exp_cols(dat), + "long" = comp_from_long(dat, conditional = conditional), + "tidy" = comp_from_tidy(dat), + cli::cli_abort(c( + "Could not find composition data in {.arg dat}.", + "i" = "Supply a data frame with columns {.field year}, {.field bin}, and {.field observed} (see {.code ?prepare_composition_data})." + )) + ) + has_lbin <- any(!is.na(out$length_bin)) + if (conditional && !has_lbin) { + cli::cli_abort(c( + "No conditional age-at-length data found in {.arg dat}.", + "i" = "Tidy data need a {.field length_bin} column." + )) + } + if (!conditional && has_lbin && length(unique(stats::na.omit(out$length_bin))) > 1) { + cli::cli_abort(c( + "{.arg dat} contains conditional age-at-length data ({.field length_bin}).", + "i" = "Use {.code conditional = TRUE}, e.g., with {.fn plot_composition_andre}." + )) + } + if (!conditional) out$length_bin <- NULL + } + + # Filter to comp type and fleet + if (!is.null(comp_type)) { + keep_type <- match.arg(comp_type, c("age", "length")) + out$comp_type[is.na(out$comp_type)] <- keep_type + out <- out[out$comp_type == keep_type, , drop = FALSE] + } else if (length(unique(stats::na.omit(out$comp_type))) > 1) { + cli::cli_alert_info( + "Both age and length compositions found; use {.arg comp_type} to plot one type at a time." + ) + } + if (!is.null(fleet)) { + # Match either the fleet name or the fleet name with its partition, + # e.g., "Fishery_current" or "Fishery_current (retained)" + fleet_base <- base_fleet(out$fleet) + keep <- out$fleet %in% as.character(fleet) | fleet_base %in% as.character(fleet) + out <- out[keep, , drop = FALSE] + } + if (nrow(out) == 0) { + cli::cli_abort("No composition data remain after filtering by {.arg comp_type} and {.arg fleet}.") + } + + if (normalize && !prepared) { + # Compositions are normalized jointly across sexes unless each sex + # already sums to one on its own (single-sex compositions, as is common + # for conditional age-at-length data) + out <- out |> + dplyr::group_by(dplyr::across(dplyr::any_of( + c("comp_type", "fleet", "year", "length_bin") + ))) |> + dplyr::mutate( + sex_group = if (length(unique(.data$sex)) > 1 && + all(abs(tapply(.data$observed, .data$sex, sum, na.rm = TRUE) - 1) < 1e-3)) { + .data$sex + } else { + "joint" + } + ) |> + dplyr::group_by(.data$sex_group, .add = TRUE) |> + dplyr::mutate( + observed = .data$observed / sum(.data$observed, na.rm = TRUE), + expected = .data$expected / sum(.data$expected, na.rm = TRUE) + ) |> + dplyr::ungroup() |> + dplyr::select(-"sex_group") |> + # 0/0 when a year has no observations + dplyr::mutate( + observed = dplyr::if_else(is.nan(.data$observed), NA_real_, .data$observed), + expected = dplyr::if_else(is.nan(.data$expected), NA_real_, .data$expected) + ) + } + + # Calculate multinomial Pearson residuals if not supplied + need_pearson <- is.na(out$pearson) & !is.na(out$expected) & !is.na(out$n) + if (any(need_pearson)) { + out$pearson[need_pearson] <- with( + out[need_pearson, ], + (observed - expected) / sqrt(expected * (1 - expected) / n) + ) + out$pearson[!is.finite(out$pearson)] <- NA_real_ + } + + out <- out |> + dplyr::arrange(dplyr::across(dplyr::any_of( + c("comp_type", "fleet", "year", "length_bin", "sex", "bin") + ))) + class(out) <- unique(c("stockplotr_comp", class(tibble::as_tibble(out)))) + out +} + +# Columns returned by prepare_composition_data() +comp_cols <- c( + "comp_type", "fleet", "year", "sex", "bin", + "observed", "expected", "n", "pearson" +) + +# Find a column in dat ignoring case; returns NULL if absent +find_col <- function(dat, options) { + nm <- names(dat) + for (o in options) { + hit <- nm[tolower(nm) == tolower(o)] + if (length(hit) > 0) { + return(hit[1]) + } + } + NULL +} + +# Pull a column (by any of several names) or return a default vector +pull_col <- function(dat, options, default = NA) { + col <- find_col(dat, options) + if (is.null(col)) { + return(rep(default, nrow(dat))) + } + dat[[col]] +} + +detect_comp_format <- function(dat) { + if (!is.null(find_col(dat, "Yr")) && !is.null(find_col(dat, "Bin")) && + !is.null(find_col(dat, "Obs"))) { + return("r4ss") + } + has_obs <- !is.null(find_col(dat, "observed")) + has_bin <- !is.null(find_col(dat, c("bin", "age", "length", "length_bins"))) + if (has_obs && !is.null(find_col(dat, "label")) && + any(grepl("comp", dat[[find_col(dat, "label")]], ignore.case = TRUE)) && has_bin) { + return("obs_exp_cols") + } + if (!is.null(find_col(dat, "label")) && !is.null(find_col(dat, "estimate")) && + any(grepl("comp", dat[[find_col(dat, "label")]], ignore.case = TRUE) & + grepl("observed|proportion|expected|predicted", dat[[find_col(dat, "label")]], ignore.case = TRUE))) { + return("long") + } + if (has_obs && has_bin && !is.null(find_col(dat, "year"))) { + return("tidy") + } + "unknown" +} + +# Standardize sex codes to "female", "male", or "combined" +standardize_sex <- function(x) { + if (all(is.na(x))) { + return(rep("combined", length(x))) + } + x <- tolower(as.character(x)) + dplyr::case_when( + is.na(x) ~ "combined", + x %in% c("1", "f", "female", "females") ~ "female", + x %in% c("2", "m", "male", "males") ~ "male", + x %in% c("0", "3", "both", "combined", "unsexed", "u", "all") ~ "combined", + TRUE ~ x + ) +} + +comp_type_from_label <- function(label) { + dplyr::case_when( + grepl("len|size", label, ignore.case = TRUE) ~ "length", + grepl("age", label, ignore.case = TRUE) ~ "age", + TRUE ~ NA_character_ + ) +} + +finish_comp <- function(out) { + out <- tibble::as_tibble(out) + out$fleet <- as.character(out$fleet) + out$sex <- standardize_sex(out$sex) + if (!"length_bin" %in% names(out)) out$length_bin <- NA_real_ + for (col in c("year", "bin", "observed", "expected", "n", "pearson", "length_bin")) { + out[[col]] <- suppressWarnings(as.numeric(out[[col]])) + } + out$fleet[is.na(out$fleet)] <- "All" + out <- dplyr::filter(out, !is.na(.data$bin), !is.na(.data$year)) + out[, c(comp_cols, "length_bin")] +} + +comp_from_tidy <- function(dat) { + finish_comp(data.frame( + comp_type = pull_col(dat, "comp_type", NA_character_), + fleet = label_partition( + as.character(pull_col(dat, c("fleet", "fleet_name"), "All")), + pull_col(dat, "part") + ), + year = pull_col(dat, "year"), + sex = pull_col(dat, "sex"), + bin = pull_col(dat, c("bin", "age", "length", "length_bins")), + observed = pull_col(dat, "observed"), + expected = pull_col(dat, c("expected", "predicted")), + n = pull_col(dat, c("n", "sample_size", "nsamp", "nsamp_adj")), + pearson = pull_col(dat, c("pearson", "pearson_residual", "residual")), + length_bin = pull_col(dat, c("length_bin", "lbin", "lbin_lo")) + )) +} + +comp_from_obs_exp_cols <- function(dat) { + label_col <- find_col(dat, "label") + dat <- dat[grepl("comp", dat[[label_col]], ignore.case = TRUE), , drop = FALSE] + type <- comp_type_from_label(dat[[label_col]]) + age <- pull_col(dat, "age") + len <- pull_col(dat, c("length", "length_bins")) + bin <- pull_col(dat, "bin") + bin <- dplyr::case_when( + !is.na(bin) ~ suppressWarnings(as.numeric(bin)), + type == "length" ~ suppressWarnings(as.numeric(len)), + TRUE ~ suppressWarnings(as.numeric(age)) + ) + finish_comp(data.frame( + comp_type = type, + fleet = pull_col(dat, c("fleet", "fleet_name", "module_id"), "All"), + year = pull_col(dat, c("year", "year_i")), + sex = pull_col(dat, "sex"), + bin = bin, + observed = pull_col(dat, "observed"), + expected = pull_col(dat, c("expected", "predicted")), + n = pull_col(dat, c("n", "sample_size", "nsamp", "nsamp_adj")), + pearson = pull_col(dat, c("pearson", "pearson_residual")) + )) +} + +comp_from_long <- function(dat, conditional = FALSE) { + label_col <- find_col(dat, "label") + lab <- dat[[label_col]] + # Conditional age-at-length rows are labeled "conditional_age_composition_*" + is_comp <- grepl("comp", lab, ignore.case = TRUE) + is_conditional <- grepl("^conditional_", lab) + if (!conditional && !any(is_comp & !is_conditional) && any(is_conditional)) { + cli::cli_abort(c( + "{.arg dat} contains only conditional age-at-length data.", + "i" = "Use {.code conditional = TRUE}, e.g., with {.fn plot_composition_andre}." + )) + } + keep <- is_comp & is_conditional == conditional + dat <- dat[keep, , drop = FALSE] + lab <- dat[[label_col]] + value_type <- dplyr::case_when( + grepl("pearson", lab, ignore.case = TRUE) ~ "pearson", + grepl("sample_size", lab, ignore.case = TRUE) ~ "n", + grepl("expected|predicted", lab, ignore.case = TRUE) ~ "expected", + grepl("observed|proportion", lab, ignore.case = TRUE) ~ "observed", + TRUE ~ NA_character_ + ) + type <- if (conditional) rep("age", length(lab)) else comp_type_from_label(lab) + bin <- dplyr::if_else( + type %in% "length", + suppressWarnings(as.numeric(pull_col(dat, c("length_bins", "len_bins", "length", "bin")))), + suppressWarnings(as.numeric(pull_col(dat, c("age", "bin")))) + ) + long <- data.frame( + comp_type = type, + fleet = label_partition( + as.character(pull_col(dat, c("fleet", "fleet_name"), "All")), + pull_col(dat, "part") + ), + year = suppressWarnings(as.numeric(pull_col(dat, "year"))), + sex = as.character(pull_col(dat, "sex")), + length_bin = if (conditional) { + suppressWarnings(as.numeric(pull_col(dat, c("length_bins", "len_bins")))) + } else { + NA_real_ + }, + bin = bin, + value_type = value_type, + value = suppressWarnings(as.numeric(dat[[find_col(dat, "estimate")]])) + ) |> + dplyr::filter(!is.na(.data$value_type)) + if (nrow(long) == 0) { + cli::cli_abort("No {if (conditional) 'conditional age-at-length' else 'composition'} data found in {.arg dat}.") + } + # Proportions in repeated rows (e.g., seasons) are summed; sample sizes + # and residuals are averaged + wide <- long |> + dplyr::summarise( + value = if (dplyr::first(.data$value_type) %in% c("observed", "expected")) { + sum(.data$value, na.rm = TRUE) + } else { + mean(.data$value, na.rm = TRUE) + }, + .by = c("comp_type", "fleet", "year", "sex", "length_bin", "bin", "value_type") + ) |> + tidyr::pivot_wider(names_from = "value_type", values_from = "value") + if (!"observed" %in% names(wide)) { + cli::cli_abort("No observed compositions found in {.arg dat}.") + } + for (col in c("expected", "n", "pearson")) { + if (!col %in% names(wide)) wide[[col]] <- NA_real_ + } + finish_comp(wide) +} + +comp_from_r4ss <- function(dat, conditional = FALSE) { + dat <- as.data.frame(dat) + # Drop conditional age-at-length data, keeping marginal age comps + lo <- find_col(dat, "Lbin_lo") + hi <- find_col(dat, "Lbin_hi") + kind <- pull_col(dat, "Kind") + if (!is.null(lo) && !is.null(hi)) { + is_age <- if (all(is.na(kind))) rep(TRUE, nrow(dat)) else toupper(kind) %in% "AGE" + range <- dat[[hi]] - dat[[lo]] + multiple_ranges <- any(is_age) && length(unique(range[is_age])) > 1 + if (conditional) { + # Keep age rows conditioned on a length bin narrower than the full range + keep <- is_age & (if (multiple_ranges) range < max(range[is_age], na.rm = TRUE) else TRUE) + dat <- dat[keep, , drop = FALSE] + kind <- kind[keep] + } else if (multiple_ranges) { + cli::cli_alert_info("Conditional age-at-length data removed; only marginal age compositions are kept.") + keep <- !is_age | range == max(range[is_age], na.rm = TRUE) + dat <- dat[keep, , drop = FALSE] + kind <- kind[keep] + } + } + type <- dplyr::case_when( + toupper(kind) %in% c("LEN", "SIZE") ~ "length", + toupper(kind) == "AGE" ~ "age", + !is.null(lo) ~ "age", + TRUE ~ "length" + ) + fleet <- as.character(pull_col(dat, c("Fleet_Name", "Fleet_name", "Fleet"), "All")) + # Add partition to fleet name when a fleet has more than one partition + fleet <- label_partition(fleet, pull_col(dat, "Part")) + out <- finish_comp(data.frame( + comp_type = type, + fleet = fleet, + year = pull_col(dat, "Yr"), + sex = r4ss_sex(dat), + bin = pull_col(dat, "Bin"), + observed = pull_col(dat, "Obs"), + expected = pull_col(dat, "Exp"), + # Dirichlet-multinomial sample size when available, as in r4ss + n = dplyr::coalesce( + suppressWarnings(as.numeric(pull_col(dat, "Nsamp_DM"))), + suppressWarnings(as.numeric(pull_col(dat, c("Nsamp_adj", "N")))) + ), + pearson = pull_col(dat, "Pearson"), + length_bin = if (conditional && !is.null(lo)) dat[[lo]] else NA_real_ + )) + # Single-sex male compositions are reported twice in the Stock Synthesis + # composition database (once for each internal sex code), with identical + # values, so keep one copy + dplyr::distinct(out) +} + +# Add the partition to fleet names, as in r4ss, when a fleet has data from +# more than one partition (0 = whole catch, 1 = discard, 2 = retained) +label_partition <- function(fleet, part) { + fleet <- as.character(fleet) + if (all(is.na(part))) { + return(fleet) + } + part_name <- c("0" = "whole catch", "1" = "discard", "2" = "retained")[as.character(part)] + multi_part <- tapply(part, fleet, function(x) length(unique(stats::na.omit(x))) > 1) + ifelse( + !is.na(part_name) & multi_part[fleet] %in% TRUE, + paste0(fleet, " (", part_name, ")"), + fleet + ) +} + +# Fleet name without the partition added by label_partition() +base_fleet <- function(fleet) { + sub(" \\((whole catch|discard|retained)\\)$", "", fleet) +} + +# Partition name added by label_partition(), or NA +fleet_partition <- function(fleet) { + pattern <- "^.* \\((whole catch|discard|retained)\\)$" + ifelse(grepl(pattern, fleet), sub(pattern, "\\1", fleet), NA_character_) +} + +# Sex from an r4ss composition database. r4ss adds a lowercase "sex" +# column (0 = unknown, 1 = female, 2 = male); the uppercase "Sex" column is +# an internal Stock Synthesis code where 1 means female or unknown, so +# single-sex male compositions (Sexes = 2) can have Sex = 1. +r4ss_sex <- function(dat) { + if ("sex" %in% names(dat)) { + return(dat[["sex"]]) + } + sexes <- pull_col(dat, "Sexes") + sex <- pull_col(dat, "Sex") + if (all(is.na(sexes))) { + return(sex) + } + dplyr::if_else(sexes == 3, sex, sexes) +} + +# Default colors for sex-specific compositions: the two ends of +# viridis::viridis(2, option = "viridis", end = 0.75), i.e., "#440154" for +# females and "#5DC863" for males, with gray for other values (e.g., +# "combined"). Use with ggplot2::scale_fill_manual() and +# ggplot2::scale_color_manual(). +comp_sex_colors <- function() { + sex_pal <- scales::pal_viridis(option = "viridis", end = 0.75)(2) + c(female = sex_pal[1], male = sex_pal[2], combined = "gray50", unknown = "gray50") +} + +# Axis label for the bin variable +comp_bin_label <- function(comps) { + types <- unique(stats::na.omit(comps$comp_type)) + if (length(types) == 1 && types == "age") { + "Age" + } else if (length(types) == 1 && types == "length") { + "Length" + } else { + "Bin" + } +} + +# Jeffreys interval for a binomial proportion +jeffreys_interval <- function(p, n, level = 0.75) { + alpha <- (1 - level) / 2 + x <- p * n + lower <- ifelse(x <= 0, 0, stats::qbeta(alpha, x + 0.5, n - x + 0.5)) + upper <- ifelse(x >= n, 1, stats::qbeta(1 - alpha, x + 0.5, n - x + 0.5)) + list(lower = lower, upper = upper) +} + +# Aggregate compositions across years within fleet (and sex). Years are +# weighted by sample size when available. +aggregate_comp_years <- function(comps) { + comps |> + dplyr::group_by(.data$comp_type, .data$fleet, .data$year) |> + dplyr::mutate(w = dplyr::if_else(is.na(.data$n), 1, .data$n)) |> + dplyr::group_by(.data$comp_type, .data$fleet, .data$sex, .data$bin) |> + dplyr::summarise( + observed = sum(.data$observed * .data$w, na.rm = TRUE), + expected = sum(.data$expected * .data$w, na.rm = TRUE), + has_exp = any(!is.na(.data$expected)), + .groups = "drop" + ) |> + dplyr::mutate(n = NA_real_) |> + dplyr::group_by(.data$comp_type, .data$fleet) |> + dplyr::mutate( + observed = .data$observed / sum(.data$observed), + expected = dplyr::if_else(.data$has_exp, .data$expected / sum(.data$expected), NA_real_) + ) |> + dplyr::ungroup() |> + dplyr::select(-"has_exp") +} + +# Facet a composition plot by the variables that vary in the data +comp_facet <- function(vars, ncol = NULL, scales = "fixed", direction = "v") { + if (length(vars) == 0) { + return(NULL) + } + list( + ggplot2::facet_wrap( + ggplot2::vars(!!!rlang::syms(vars)), + ncol = ncol, + dir = match.arg(direction, c("v", "h")), + scales = scales, + labeller = ggplot2::labeller(.multi_line = FALSE) + ), + # Fleet names with partitions and weights make long panel labels + ggplot2::theme(strip.text = ggplot2::element_text(size = 10)) + ) +} + +#------------------------------------------------------------------------------ +# Internal helpers +#------------------------------------------------------------------------------ + +# Prepare comps and keep rows with residuals, or abort with a hint +comp_residuals_or_abort <- function(dat, comp_type = NULL, fleet = NULL) { + comps <- prepare_composition_data(dat, comp_type = comp_type, fleet = fleet) + comps <- dplyr::filter(comps, is.finite(.data$pearson)) + if (nrow(comps) == 0) { + cli::cli_abort(c( + "No Pearson residuals available.", + "i" = "Supply a {.field pearson} column, or {.field expected} values and sample sizes ({.field n}) so residuals can be calculated." + )) + } + comps +} + +# One-sided Wald-Wolfowitz runs test on the signs of x (in order). Returns +# the p-value for fewer runs than expected (positive autocorrelation). +runs_test <- function(x) { + s <- sign(x[x != 0 & !is.na(x)]) + n1 <- sum(s > 0) + n2 <- sum(s < 0) + n <- n1 + n2 + if (n < 3) { + return(NA_real_) + } + # A single run: probability that all n signs are the same by chance + if (n1 == 0 || n2 == 0) { + return(2 * 0.5^n) + } + runs <- 1 + sum(s[-1] != s[-length(s)]) + mu <- 2 * n1 * n2 / n + 1 + v <- 2 * n1 * n2 * (2 * n1 * n2 - n) / (n^2 * (n - 1)) + if (v <= 0) { + return(NA_real_) + } + stats::pnorm((runs - mu) / sqrt(v)) +} + +# Input and effective (McAllister-Ianelli) sample size by composition +comp_effn_table <- function(comps) { + comps |> + dplyr::filter(!is.na(.data$expected)) |> + dplyr::group_by(.data$comp_type, .data$fleet, .data$year) |> + dplyr::summarise( + n = dplyr::first(.data$n), + effn = sum(.data$expected * (1 - .data$expected)) / + sum((.data$observed - .data$expected)^2), + .groups = "drop" + ) +} + +# Does dat contain observed and expected compositions by bin in the +# standard output format? +has_bin_comps <- function(dat) { + is.data.frame(dat) && "label" %in% names(dat) && + any(grepl("^(length|age)_composition_(predicted|expected)$", dat$label)) +} + +# Does dat contain Stock Synthesis FIT_*_COMPS tables from convert_output()? +is_ss3_fit_summary <- function(dat) { + is.data.frame(dat) && + all(c("module_name", "label", "estimate") %in% names(dat)) && + any(dat$module_name %in% c("FIT_LEN_COMPS", "FIT_AGE_COMPS", "FIT_SIZE_COMPS") & + dat$label %in% "effn") +} + +# Sample sizes from the Stock Synthesis FIT_*_COMPS tables in +# convert_output() output. Each composition is identified by its module, +# fleet, year, and other indexing columns; compositions that cannot be +# separated (e.g., conditional age-at-length, which lack the length bin in +# the converted output) are dropped. +comp_sample_size_from_ss3 <- function(dat, comp_type = NULL) { + keys <- intersect( + c("module_name", "fleet", "year", "sex", "season", "subseason", "month", "time"), + names(dat) + ) + fit <- dat[dat$module_name %in% c("FIT_LEN_COMPS", "FIT_AGE_COMPS", "FIT_SIZE_COMPS") & + dat$label %in% c("nsamp_adj", "effn", "part", "ageerr"), c(keys, "label", "estimate")] + # convert_output() keeps the values from each row of the SS3 table + # together, in order, so a new composition starts at each "part" (or + # "nsamp_adj" in output without partitions) + first_label <- if ("part" %in% fit$label) "part" else "nsamp_adj" + fit <- fit |> + dplyr::group_by(.data$module_name) |> + dplyr::mutate(comp_id = cumsum(.data$label == first_label)) |> + dplyr::ungroup() + wide <- fit |> + dplyr::group_by(.data$module_name, .data$comp_id) |> + dplyr::filter(all(c("nsamp_adj", "effn") %in% .data$label), !any(duplicated(.data$label))) |> + dplyr::ungroup() |> + tidyr::pivot_wider(names_from = "label", values_from = "estimate") |> + dplyr::filter(!is.na(.data$year), !is.na(.data$fleet)) + if (!"part" %in% names(wide)) wide$part <- NA_real_ + if (!"ageerr" %in% names(wide)) wide$ageerr <- NA_real_ + wide$comp_type <- dplyr::if_else(wide$module_name == "FIT_AGE_COMPS", "age", "length") + if (!is.null(comp_type)) wide <- wide[wide$comp_type == comp_type, , drop = FALSE] + # Several compositions for the same fleet, year, sex, and partition are + # conditional age-at-length data, which lack the length bin in the + # converted output + wide <- wide |> + dplyr::group_by(dplyr::across(dplyr::all_of(c(keys, "part", "ageerr")))) |> + dplyr::mutate(n_comps = dplyr::n()) |> + dplyr::ungroup() + if (any(wide$n_comps > 1)) { + cli::cli_alert_info( + "Dropped {sum(wide$n_comps > 1)} composition{?s} that appear to be conditional age-at-length data." + ) + } + wide |> + dplyr::filter(.data$n_comps == 1) |> + dplyr::mutate( + year = suppressWarnings(as.numeric(.data$year)), + n = .data$nsamp_adj + ) |> + dplyr::group_by(.data$comp_type) |> + dplyr::mutate(fleet = label_partition(.data$fleet, .data$part)) |> + dplyr::ungroup() |> + dplyr::select("comp_type", "fleet", "year", "n", "effn") +} + + +# Composition data by bin from an r4ss::SS_output() list, as rows of +# convert_output()'s standard output. Each composition database becomes one +# module ("lendbase", "agedbase", "condbase") with labels +# "_composition_", where is "length", "age", or +# "conditional_age" and is "observed", "predicted", +# "pearson_residual", or "sample_size" (adjusted input sample size, or the +# Dirichlet-multinomial sample size when available). +ss3_composition_rows <- function(replist, fleet_names, start_year, end_year, columns) { + dbases <- c(lendbase = "length", agedbase = "age", condbase = "conditional_age") + out <- list() + for (db in names(dbases)) { + d <- replist[[db]] + if (!is.data.frame(d) || nrow(d) == 0) next + prefix <- dbases[[db]] + sex <- standardize_sex(r4ss_sex(d)) + bin <- as.character(d[["Bin"]]) + base <- data.frame( + module_name = db, + year = as.character(d[["Yr"]]), + month = as.character(pull_col(d, "Month")), + season = as.character(pull_col(d, "Seas")), + fleet = fleet_names[d[["Fleet"]]], + sex = sex, + part = as.character(pull_col(d, "Part")), + kind = as.character(pull_col(d, "Kind")), + age = if (prefix == "length") NA_character_ else bin, + len_bins = if (prefix == "length") { + bin + } else if (prefix == "conditional_age") { + as.character(d[["Lbin_lo"]]) + } else { + NA_character_ + } + ) + n <- dplyr::coalesce( + suppressWarnings(as.numeric(pull_col(d, "Nsamp_DM"))), + suppressWarnings(as.numeric(pull_col(d, "Nsamp_adj"))) + ) + values <- list( + observed = d[["Obs"]], + predicted = d[["Exp"]], + pearson_residual = suppressWarnings(as.numeric(d[["Pearson"]])), + sample_size = n + ) + for (v in names(values)) { + rows <- base + rows$label <- paste0(prefix, "_composition_", v) + rows$estimate <- as.numeric(values[[v]]) + out[[paste(db, v)]] <- rows + } + } + if (length(out) == 0) { + return(NULL) + } + out <- dplyr::bind_rows(out) |> + # Single-sex male compositions are reported twice in the composition + # database (once for each internal sex code) with identical values + dplyr::distinct() |> + dplyr::mutate( + era = dplyr::case_when( + as.numeric(.data$year) < start_year ~ "init", + as.numeric(.data$year) <= end_year ~ "time", + TRUE ~ "fore" + ) + ) + out[setdiff(columns, names(out))] <- NA + as.data.frame(out[, columns]) +} diff --git a/data-raw/bigskate_comps.R b/data-raw/bigskate_comps.R new file mode 100644 index 00000000..76daa03a --- /dev/null +++ b/data-raw/bigskate_comps.R @@ -0,0 +1,36 @@ +## Code to prepare `bigskate_lencomp` and `bigskate_caal` +# Composition data and fits from the 2019 Big Skate stock assessment +# (BigSkate_2019 in the Stock Synthesis test models), the model used for the +# r4ss example plots at https://r4ss.github.io/r4ss/BigSkate/. +# +# The test model includes Report.sso but not CompReport.sso, so the model is +# rerun at its maximum likelihood estimates (from ss3.par, without +# estimation) to write the composition database, then read with +# r4ss::SS_output() and converted with convert_output(). + +model_dir <- file.path(tempdir(), "bigskate") +dir.create(model_dir, showWarnings = FALSE) + +# Get the model and the Stock Synthesis executable +r4ss::download_models(dir = model_dir) +bigskate_dir <- file.path(model_dir, "models", "BigSkate_2019") +r4ss::get_ss3_exe(dir = bigskate_dir) + +# Start from the estimated parameters in ss3.par and do not estimate +starter <- r4ss::SS_readstarter(file.path(bigskate_dir, "starter.ss"), verbose = FALSE) +starter[["init_values_src"]] <- 1 +r4ss::SS_writestarter(starter, dir = bigskate_dir, overwrite = TRUE, verbose = FALSE) +r4ss::run(dir = bigskate_dir, extras = "-maxfn 0 -phase 50 -nohess", exe = "ss3", skipfinished = FALSE) + +replist <- r4ss::SS_output( + bigskate_dir, + verbose = FALSE, printstats = FALSE, covar = FALSE, forecast = FALSE +) + +# Convert to stockplotr's standard output and keep the composition data: +# length compositions (lendbase) and conditional age-at-length data (condbase) +std_output <- stockplotr::convert_output(replist, model = "ss3") +bigskate_lencomp <- dplyr::filter(std_output, module_name == "lendbase") +bigskate_caal <- dplyr::filter(std_output, module_name == "condbase") + +usethis::use_data(bigskate_lencomp, bigskate_caal, overwrite = TRUE) diff --git a/data/bigskate_caal.rda b/data/bigskate_caal.rda new file mode 100644 index 0000000000000000000000000000000000000000..0497ea9963b2fe93f36baa623a51d89cd1547d98 GIT binary patch literal 78488 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Т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�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

pE>l^!uF_wSj|89%D zl+AUR#qP%hQps(!F`4z#iyu+%f zccFDhm)&y<_oEv70o_Q4sMld*gZw(IIm$#(YQahqu1Z3$Vtzlq)>4dx5Qq9&)xzB= zY@bk0pKu~#nRiU0M)GVaH>tmxpP);~qv#D!VoLD(ncuaGxBJNzpJTwfPR&!7M>-Y` z=001oJF4lLUqRQZF>cuKsgh`UiIIudw2AyFEsD*`jLb&p$`k&N37(Yk{f~3iy;9H6 z<-x?F5;PUWz5;(mwUEh`*pR5YAQ<}vs^gR_OnmlJVaW8^3#9JyAF>n}mHt`JGgNfH z>EvnKteIP;VfpkgvSJr}9sSES5`PWXo@BCV<$z__T zmzA7Rip0vz_8Ft}B*>?>pWu+A40F#9ZuNUdPUri_z~aiDVp^Dk6KT1>SN%x}QC36} z&?fSF3h!y5X}T3}bRT}@=H>TLLiI!^jq%p$0WraoillQ>o$;N5bNwfQp?4jGUCQV` zmqO&eRb8dLaL)HC?_Q6go#u&`Khjx@&vMcjpKx~Oe5}++s#k=kt}VjPttPPHav8kvdhNY=hb?MYg*X4 zOXO)wcB85wLg)`%f04cwvtHm)_I&jj+-yiuxv;hx2DFI>u`(?7H(4 zIqGq@f(0793cLsYBSX~WV5uNw2}9zG*1jeviLN>~^v~|ZWD%W8{51{0F2~rcS+saz z8p)sEE=BLar_1aNJGfd(&A$%WT8|&W(xB0rMbSva1lzRh!^wV+P=hyNzHg!5#N~nW zo!*79kL&MUVrsyf(=;bfopSu-+{;}`(?kHN7`<1nDzSxar9O~*E3%If21R)OU8vphqz}Via-dD2t zm3KLC2cX-1t-P%jMp_oJ<>7UEMzZSif21!r?jS(k^W#CPsVbgaC&YC9sx__u3ycpk zPuud*kB*vMStzm;K211uwkoryrudzORrQaFqWpIO0G&X=5MVE^m$yQM2(nK z{7J@E^S4_MW#G3o&tOaLy6@X)@@$#wNqJiBZL*%0q;fvUepGN(c@eOj7dD_DlFN`G@xTpTP}i%iQDbxxb%G;9q?|2=a9_49{>AbKGf0vVKb&5)2+0N!bY z>kN^39mbdsukr35KcqkP+gjxSG~U74Ao%0-Fv{5PxegNr&v&HMq2gbt8<+Ek-=V#x zU48CuLD46^z}3+_x;`IFleYcUtO!$AETMHJyJoKAVGH>9dqAesBHFB-6m%1^-N;bq z)OXQ@lqFD*b!2^-Nq#f{zMfD8bPz&Ys`}IYptU z=J~~~erRnkF7wInCS+hT6tu0P;s7(xP}hwuU`Ad$p;)V1!T?MLkDwdo`g%zklw2Nu zl0&SDsv$Gyo*+e6p?Ky{66BGlZkA|U8IXA8T%{B5r2^v&25qp~ko<;VIit^3+bG%- zGdsd@DiX<*5_P6xLqq|x#@4(y_u1=Bv)4&x4ng}g+dSWKlaZEow-#xbe*GXhoVTg* zrP}p5+^g#=IAgUByHPk>sIvE(iPTsK{TuKBTXfhHd&z3ajZJ;mdoIb$!Zv=VBBx)> zi<73ryA^V}JyQG|_?pQapb9XBR4WwQw=t+4OkNZL)Va5jRK#p#`63JIW-?$Asi#O! zKb}VvxUEnuxa19#+=#Bzif$Y&o zWe+Pk7MWEKWdL_j-os~H35c}hhsW2{IVs9R@OUde{4HnRmeePdsB+A*GPx-g;?2d@ z!}9Q;;Q*72p7_2AE+V14hcM?IF}F#;Z6pSMl}rkJyTWL*&=NW=v`x z$L`_=%leQd#@0j}*I(Y@&By8Z%loItK=hc%#Fe5o!H{DxBnCi;Wlqwa$h>4+x;`*O zq6Ea==y<;vYRBBc2w2ApVv5GFgL}>5CGyR#9CUPGVxar9OxQoO9*_Y}W!bX$wlsOE zoapS^lt2W-;3;(Y(eTTN0$~*af?ZM3T@qGbc<_6_@W2;G8w-#9TJ=2>+othn%2n{T zcIV+$?vU6T;KKO*yO&G!04Dlw|87N9-zmkF-Bu?WQ0kE-uV#$NoaR{jD!pns+vXH7hB zVf6F5RABhTJajBSj6SbJop63XiXm6;=MZmTs*OXQZ2=CKyI{(9gqsU~}lqbLbdAq6G;wNIV>k`>8q;rr4)|cS9_OBtyv?&48 zT&Sf)UDB1S)FQ{m8nX~Zq+oXXNVFzZ8hsZ?@H~B;G6c+<8{ZUY3N(jsNQBzYe;VO2 zR+nD5W;^DULJ?6<2E=7rF#(`At`KMV_^+H7;mYAewIIOZ@8mN47M(PflEc$^k#9%h z)yUPIk|B;noJt*4MI>vdnAEKa&okE^^G&qjmj74{nUu;->yaQ@af8c{XhjwF{$hon zZ!%a-ugYq;wg?wl311%!+=!O#`D*yw6w4r;S+HnqM>Vm+$D2<$XW)eIHna-Eo`o4z z1W-~t0f>Jh!m8b8xZC0y(irOXlH)!vrVZjIVp>CZSP5wfi##a^Xy z{|exQm(5(2jk=F8 zl#RTtxYzR`{a2H8>0HB}9y4kEqYLIZV-_}kC&SV!qTgbzC4&%`4bziO!nrf6gzoq? z|I!pMeW?mu`@3#@KA<#j;tZPppSYyMA298wl4BPxYV8txIYDEb%`%Bb0T{npvJqTjI*a90M}E&t zXocLn0Q2jpsC9OWzL&87vv~FiKzRTI10}(_8Fd~i;2fn^YTL|wNQ+{tHHk4w2m*{M z4^LB`DUhL6xeV#_OUm;yBW6Z6B zY|Ah+y-i!bci)YH?PddY-+OcnEjZA`=tC55%%2@#OOf^0l-PhMoTHp27^IAp9X?d{Gj6qhkgnZu{8Lji!PVtvpXJxb<{lTQ}4e zMOw95<`>CY&)&H?Q!c`7GiN(Z-8ebw)lTy&&oy7?y!~vRm~J_9-3a!u33_R@HS1G93_qIL7-E)1DHiY$3 zDs$Rw&L)C>5ww%8!15vtJKg92I?AdJCz(XBwSzH0|@DIA}XoaxvtNX}$U3=+-Ll zlzsD!OOyea8zG#vL~=Oz=VhiIbS3%(glNMzVU(y4ga2dvHC`gor-rcwC^@F&hmPQ7cmRgda0Z21Fo8TvZaE7drom( z)d2UUk`{~ifZ52g0ToOe6hRx|oqlPaoylR@Q|IPWxxt*+?$1=S6}CK2949gPDax5y z417>Y2yRtBJXe+mI>J1%VxPc#aQ*O|Du4FV46qTDD|2@Z-UWV>X>Lq|sOqwfqQw$i z@latE=vWh{Z~Zsl2&w$r_jt7%!~IhGS=d4Sr=RES$HCL5oit?iQt;=yV>8lZD$Gqv zK=%ql{t0fYAyTYQXDt)vD)j~r=d}hvpN3wo`CFTPlDA7pdSc*Sc1CLqOE=E7f;`{O z%rbH71sx~D3WivJQ&wk#z%6^uU5oY47D%LYobS6knzVuZz^qr9$*eiAKd_=WV(wXq=nx)iHttV%Igew4{s=XRQ`g@|}7##fn0UYri# zNL!9y`l)&A*YpGbDA(Xi;N+-tDCKlwrSj|vo;bR>xKh^BxFkSY?2$Y8Hgpxt`29f) zZDY=SinPZ*Ls72ag8*n3eHxhoT6pIQh-FjOq=Q?!{abn3)R99n6oEYE<3hnwSi8x>mMn9Onwa|d3#H&VfEsUlHAxOUt`X_jr*^j0bO=hp zo}`{pr=@x-`OO4)`O0+cKGV%A5=;fye;qotkrrfk4?`?)$~=H?T@qCp(~xLR2L-@1K3%#_emA3sJ`Q_;rBIu}8Y z-kyo906~N&ag4CM8#5>~48qJ!5H^8{P$0Y`V|wd>Kv8B{88(R`B_VwER^SwqBaK_= z(6z-#rIwMz`@~ut_B*qAlZZZ+b z6qXByd8prvDH6Yi6wC0jNN(nA=gKU}Z8h}tHd2(66F&_&n_&Ki(c46zl-Gt(OB-zJ zI!&Dy(!BV7pkEOCT;Gk^xjaJ9SwV~Xs7D(Tv|NejEYMq4SUwMldR*oXoc?tdsOz|e zJt4^8mZXn${jR=>C#Nf*zF-9iQH8{CFr1TkmWa2<{Y6-?F3bj6_{dr`PQZjxv5_xY zBtk2e6K~~n35+gqoI)l#=^6Oqcs)=Va-V@A)@1hm9{NPzh)3ImgI#i8LZ8jSAn3pd z>WMcRyfxHF`JPsNqNQcojw??IAd$VmC)JelC|{iQliIw0q9<@ z*uimWTL+P!)j9gSu8DYE(r?u(RQ!Z@KfzZ4kvo7ix2D zSz!Yk67+6Or;@HuKDI|*4T*pfLW$(qKIb}1MZxssYX~sr>lmbM`Q+`$-fu7ooZhq9MF83wgUC60=O&%yZ3I6Lo#PNNCL2E(_u$~u~GPC)Wp z>uWc!AZ4=+QzNL)91|{IjQu~*uq98jyBXWi2oh2aUl3 ztuWgvd6CvFwY-?0M3Z?aX&_n=biQfKVz5fMLT4D8GE!$eP{2*G(`wJ%Kps2d) z#(ajMfCNBek7~do1V5hHDTi!o=1H{LE%YKEvW#Z6{gxy2g&CH-xBa^5!Zb_G`!~yF zO_&_#89dy1Y-g9sT>sb0w$=OF_r#a$sS_dC<$qVTr$fEA6kV*d%(I&ve()lpz`pXB>E6)4K1WMFmP`gJMc_m00af_9tQrGszy$@KU{IL) zuHc1EJ9^%v(vh@l5ZWH|B`5{+>0mgS8<#eJj~m7by#JxhzN!IEYU<97d@Z53)LzK^ z;)u<+v2Grtfb{2&jAR~DNMDsTeFl~r)>k^xnyb-vGoQBJwt_f7I3-E0k!3I!SwlJ7 zJwY_1>FO~}BIcwkC18`DoJ|0AR-F-}mICH8K}b2Eg7Gv3JdR~t*X8X3x|=hS)fxz+zS4v+n0 z-c+^#jlEsDXes}6QYAN;79r?Q>pJ~lgO0$z>-t1`L=$HVIbcL#h$SN zF{?x5L@zg)-C^y<1)pD-w!21z6K?{h+|wGxxfjVjRy=~4F9U*#{Q?EGg9-Hbjl~no zu-ii7V_i)%_`w9q$x3mn2J(y-R8C`(=XKsNV{l;eH87ZmflngNso_p3WWdH>6ZbP< zv5mK(Q5s5`#RTtr4fr^4hE20!xzW3xE;ZJaM5-zS2E*s*2t0ch%9ElI1HHC95HFmU zr)x<)m&kK=x=x(H%yXDt@z87jy@b^9a0a0WFXjMd9CN{G7z%P*y(3Y{cAX*+#Zzs1gm-MXlLq)w@|+ zNY6qNa}@aerxmB7Kkyq-*p_PPB(c8!qMOFyuEI;B2tV;k5aMm1Q2dSa^D+Lfldv)+ z?tJX4ErLP}`NjemS82Muj^(4%o)z)Ts4qcTi|s2}F%pQDAi||O&fUzeaHU!|sG^fL zET6*m3whTv`#G|sUN;a>P}gIb>pM*O5Au%}-qpFE}mqtRNYXEj<>{khA`@yS7!qwxsL18WMyO|0LxnK~glYCNR?o z!Yrnqte}f5)x?KIgnpRh#>Sj=@A&P4(gnN<#ia|PQPpS5!|VT!$b~VoqS0VX{Q4vH zMRvCHt~CU`0+JHq+-H2Pa#gO0#*YKjCC8^~ZKvUqfEluw+(P%2f7if%mXC@Da{fdt zn0Px*KvdslezW*78Pt40Q3JtJo1kuZRiXPg8%Gghy$vreUa|M0*lBNc~5; zp;)$um(Zn-%*c$L)zd2HqtkfCU7fxfO$GGB_BaziFL1&wg?|uJN+OU89V24|aa5t3>kcldtkYsN=!-42w0t{}wPnbhkAG=YN)NLRJo# zjzhx_(SMK(v(oD5S_1ZA4*0k$Fr5jJL?NT=CDW&%xB zF;qv)UqKPGRdUJa9iL(7M}Dva|4iZR+<5^PTN!WHtHMWtPlRV;+Z(aE+d{+Rk=9Vp z{Mi_byLO#|#CPMRFch{t1jXR&xzt-@bzq>GppReBtWGj3T3upLn?yZS=o$E&zumEG zRS%*{WkVGY_NqfH!J|KTVsVL$=^JdTb84k#SGW-Ab!3)Sl&0$EQ?6}F@Rz_PI=5=q z_5z;~h3?xU$d^BtbZ0U@`gde;w#df11-n#K;*!eOY_%LhF^?R|Q*MlO&)8x8sd5we znjN%?lU7)r&pAUou$y%0GIhVN1#30MxALWI%ss2NzF0b{r|QTheV`yM%5YK0<<&}4 z@<7d@{U8Z`B(G{)VF_TtPm>c`U-FNM9FF}Ar_cBC~B5E)iExQqZ@8S_l6usZ{E6&89rRS%Dy=9K)9?#QH)J}O$fqp ztx@~Nr@W7br~O96X~;m^aKSP~VtTRc;;yF6JA}(cUE%eI;iX{gVXPJT+YI}%pK5SO zp7DBv>rs_!?x)YGd#`XlW|qH9tdCHpy6^^z8=!dD(+aI55E!Y@vNDcaiMU-ifPS@C z&JFX#Z>Y@`T;rvDH>I6|cn%#EftnKTUFg9R{|Hy{s;CRZLP*oy-m@Hw;Za70J%ZLZ z3$j+0;rkz*z3P~l!nRAWL|EZ)wu>=$2i6ChSN^#vVfwuE@sc!n@S zAPMQB7ZO8~m~pmAcr9&^k&UbdS!zGOxk1KdF5N@|bT5biYdpli#p4DOT$5{X6)3i9 zYN)WhtlW=TP%yY1%V~+A{(CNhl#uGYmQ&J3ndpqy|eR|)gN~7QP z2$}n+FNs-tCCQcm9QX9l!5vVh#TMS_!^iH7Gg)%qnY&2Wj0>4uS0pu8)1SYL&5Vus zI7-{)6)`H0zZqTo-QP;t3Q3F46EU!YLOb!6}BG&~Nw3j~`Y zHHCL5S*XT2i^vlvN`pwNkcM=Ua&4=;-`UJr&x1022%fK$7+as#Cqyua*W@*MaDp5R zElh)?R7)QRyMSHK27=3e9Uu+%720O~AgR+ys$DHxW@w>nrHiWwDA;w_{v&?UN-$#@ znM~im%v;4**v?L6C|^%8`8h_i!&~-`=%GB$$8KQS2o$j7j7=!HXAY?SrXCKvtMPq? zQ9r7_!@;7otXfrav}<%Gk9U=pzT7y%0lvzK3*OmVMj2~W!j;=Fm^n9a6d41P8BvS> zd7{4gaxJKE$i+M5HJ|Vm5r(x%&})Aisikj^-0G1fipZ=7U^yk)9iav&w%p4qtG(!O z-s0UWS>;ejyd0_YvzY6#gm;lGr!jZptkCm)rVG69pUjI~{-xQc$5E>wMv49*^K!DQ zlLZu(4p1|_k1X>Zxy??GT&rl*X=7Krpw6YG2r9HE&o1;TnRAR`slH)D1g{ZX+B&DD zU5*xUzm6>W+%c-E&hiCBKl|d2{RV+rhsCBazVjSrf>*Qj?$&2RzkKT=Jtq_exswRz z#2U>7B?2vf6)K#Sx~b|dcawC9iOBwhQXImKuQit_O3j!5_FIyXh}`GZUqPnEIy~gq z|CPf&*F(Mhy|*rXEI@0q?|?`i2Gi$2elnBz37+#}4;O30>P-!@|B6+)8PoU;nY(PG zk)9%}*Lp3E9IZ(cIJ1kTU&2|D$|f7S4E7b(SjE~z#UX_pmGUbN5R7e$x+hG!-qX+G zy97pnL6x5hsRl+i+18XN@#F4C4YJ)`u_uP|7Wk8Rg0yE6CWm>+IA_sK}3uxAtg zC2RL0s_8+DT?ve6Q;rFH4XU@xc1TsgE2*=1Im4<%YQf?!wizsOOqR#-Qe4-9%bszA zJo@@<_!qR6v1}_J%R^C7;tbVYn1D=7f;~|&Zu+mhK&6AOGxQz}J~I@z)Iw+2bo^~q zf&)Ji4Yzqy1>z%!E;I*TY%|pW-Fxp!MXQN?Nu}RzR>3n?f`8xqvU-Dh{#K*1R3G_X)f3zlTv_^x8|qr6zYdW+-$bax&`HE%~9PGx?NJ zP{XqWG&kSCh#`_0fC)1V;V6IZC)hdbofZa2>+nqiDJV$sL1)2ld%vEo?tBiTAF4k$ zXGQ_=_D9wEL4m^;r(@fZ9f$~Qu|9;* zIl&V=1>@^>aL)NH_kevwdAZDIUFtS9V=ddgRiO<}lN=r;k4Dd)enajP0|=S}{bkyU=sG?tJo%WlWBtSvQjshNXbONz~Stb1}olwf`&> z0n?H7Ux5tPwvwywVha}tDneti3n$EJPJi%{a=YB2nnIl3)}HKvv(ql!3CX#cY8MmZ zS?$1sCBX`;?X;e+3@h4h{v`+uSX%ynrMw}&)EMw@pAK0wyf6({gsDxm1sf^k(GzrI zrOWsa;+L?@x3edao%Yk}J%iDRZOV_yN2yGijb=Tef~x@!m-RIG-=iTpzclH;uR&pi z2IGuU_9{d0ep-5JCg2)h-pr#L%suN;6;GP-9wOk_Nc-oFkq{FrYC}ks3Md3EThNvkP)xffx_6;h)*E)c1T3of z44;Z#ILTPicZoQ+zK6#XB(+N{2H_k*@p8&&s_)X8FqU(+;kRUce{Sj_+}bhoU@1d3 z03UfjUJGy;du65E<_y%gi__M;DQ@22;`BU8;({1U$uG-|hdbgl#@CEAj3Xoh8*zR~ zrDF!}F`-UkG-J*td7)#g9wzR-Yc=IHZN7a(5YpTf8 z-ei^4GVhBBcHh~q44j!#juMYy+IyM!H+ong@J8zLG>REY`$bF$%XD>C1tYhlzEcUL z5k?{jwa}%QLU0q=YWLG%EG$D|UW51bdQpzvAHg7PA)r1Z)zdii`s~+lV3#jMGm&Oe zMpaGiRZ(|{85atE8;*s=m9MJlIvK>(ogt(E#KEWRWm=|cx5Uvu0g~jU!Ma=P#Ufxm zIKMo`ti2Hqa7hPGyKt4z`3MoB&728F93GynLK+1QHGb?{o`WF-Rb1w z!cs8gJaD&Sk|H-pQmnB^Ax8uaIhKD)*oBdy*IdRK<7T)z_=r#OlvWAs`rvUUigXOu z?Z!xU&|(NU5aQr<%=>f>l-cr*YY09lJ#tEH7a6~KuQDLZ*bsNr&s(4;0kJ)fi1g(Y zNXN@j6%F@n=E>l@bWC(ZUK06P?#^^4i4#17AK)dA*H3;5VB#B|kzkE8uCpjt86qJT zj!XT8M7O!QW=gK|9do_8$=BIxz@9;drpoAL1l$M3aof0kf&268IlC0Nx2!XAup9-A z%2ZYj51JxjV{HF=VKT!fO*csKosM@5IQ;hIXMckTo@%}5ZHBWFV3~`H38a>(qVD?B z^ki%%F6!a%eKWl`nURn0v9^?*b{1VNEcwbYA!#n?`^9Yql>Fl4Le{Df zfh6Bs_RRO}3)jv+NMI~FormUTQi{(}($&hfSqw$*Wt=O+nPpc%_aU~1v2H{Yj8+$* zP;osWQxI-y(8Wz^t}Go89cU$bQVk$|BWk0=;-J&XxLLpev{!V`(=I1J`YBjUUS(Qp zL1fvoE}n6gWo6L~1-CNcIw6Tvj2P64X``z2`#fIr8nmYab+S2`c9@$K`IFv_?ww+a z5Y+NWp?5tqI;!))+ADNkJ7-L;IJhmBp4-S|vIoUx<8O?{xaz|Q!wvvvZ`cr#W|VTj z`SWXU=7Mu5t~{W;TVr%mCxX}{tJ}l=FB@3VL$LIe)0W4Ilf68l?YE;n4`y>Ly`CFL zDY)=a?tTQW(V*3I6MTkalvMP;MrNSNX^*#5KOM6jzh*vnc-$&iBMic=x8FiT89ka0 zEwL+mIj4L|ulzIh{!%^ncaouxy6q3j4npus1X->)|CgbRC?--LqxT+gSNIz{-W@vY zsS~dXDM;+}-|ptt^%bI&_{HqQ*P7Q(WhLKAua#MAk6^ugW*7UVRS2|of#p%Y@-O>k zMRs)i!{(lM6QCerQa>gsD>94~i@Ut_g|q1zb_EHd9bmXr_k4|ibfPc1nC=GCXmBQz z@YhFbg*80p9jPC4@cG}^q}(N(CYK=Sb1z*0U+i~-SdU4Z@VD_Ne^wt+=w*N zn1mhZ$1+wPx)HvVMzcG8#j!TA=oB71hbuHiOrVuOg>CPHy+m?4UMDrC-YY&M%Z*@_ z2CpcJR*G=uSw;L6Q*K9%me#MIm@;D!U47>!t`vJ^-gw}KNj z(6&+fC#TU(N9ygoo>xfso_b=ATIsEAYz$NVFQ^%Cc^sRv2=-pzJ-ERW2#=(W4YgjnIoMiQ+W zI~R8)fXIT(zX;uDqE9bKwFSg}YCPBk#M65`-HO%$wA!gH@M_GsC1s-I1ZXej0Cj;; z%$5lW5fk)V!3B5^E}mcf(K}p1ubD7ZOR*2pv;;UvLjx*=2@pl!l_5@21-igf;swkK7w_yV-EM{N;M2qZ)IamToVttri!Of z09BMk?&pC!9t1bSm4W(z=_BJ%pK-dKYzo(*l@03B^zO1r2{5~1eNb69F3(1sunO+? z`j(l=!;KHX-0NQshg%V(GQCPJ_|QY9uS=H`fbzgEO>0DFxi3c+9b#i7V6c^N{F%k@ z8bZ_B1sQpnM=>EWdC0zW!?l>&UJQD z*0~X`+ZJ+XKS7E1DYOU2owPNihR>N>Gib3sTc!590RU#eO&`C#wc8fO} z`~+rAkRie0mV~oz73P9hTRr<=&3PphXU9Yf_q-7rB8dO{$x&2UDnoY5F?}SUId^4s zQQ!G3S0)4OCI^&S2F~3#zo%|;YSX%N>pqqC}_(4vW8&*&V9LURc&i7ekG*LYkhL(Ra z@Uy?E60Wwgt2{z5%1K`buB!9)C|(&>*j3I`ZS$M9)3TokeyN&L+SoGX!^_~=(A_X# z3~uy<1JT-(PtcR7MsYyUC~^mt0{vbQNjyV1qC9Bjb$CHyWT6V2oyM`j-9>xCOb4R` zO%|tW0J?h^*IgYbFwZ^wL_u#FW_R~k2DX*Q=0???pACDd&ql7o41`2#W;FJ`uQZ&+ zohNbH;>{&!czt;VMR{QtSl746)S3*Z3|3YG`f!JXONH2F13O@|0Y+BOUjPJ+%h$#G zBTbqD&}qOSVRMB}&?O0vNIzrAUaz#pRfB`Q3f%pb>@-on`^a>OkO>ZX;^JeRVFY}& zzGDRMoS)9c^e^yB2b?G2Da-?4M z4Y!79N|Ihvq_hWX28a{El=JYMpTKnL$ZfyDIdvMSYAvZ>d#cLhKl$ryrmn|c z*m+Ce#zN`0!q4InZ142E#Bw_`DrPnlK7KnYTL@?r^y`5Co_#YP}XV>(1|x0agyJ6{R9Nkz9FKWhY*jX zk~hStIB%N98&GU|COhfhV2Pz`1PMWJIew<*!894nW{ebl4xw5T?WqoDe$g2y*CT!L%%(dRu4l zbioN#T{s2nIX4CVC2U$ZLf6^Ka2;cBOShZx$*^g-xR`cWoG8)oKOX7?ImvAK(|OA~ z*AHiU>@~N$ZOQfjYui0=6m^eP@p+Bs?~c7v3t{22=J1I5&kLE3u>bXp!9(2P6fXz$ zLAAC3rq%0PP-SY!-w{A>mnxPzb{LG^?8fMT7JXm52PZsaqC5Ui)bvQc!okFqIuT%C zPlCcP)23c{QJ!54FGOhW;}iZ(;yEeB5L<#gtG)NV>Fhz<+()<{JFskNSk|C}Ibf>33VsZux8Dhq0NI@(y(H{$#br4!SEhlSmLG5LJY9wlKMkS?lWP6A4QR=#^fo@Z<8pHg3DjS zK2ipdPP`!?6gmdznqrMzoB`6v@{Hc0$F^ho6_N#)ZwuNIaQ(a$ouLsJz*YPPI8{9E zUOXJ}#{_biebdDD8;0AocSAbhb`3nGZ|;h*sWj)$z8}ufNBrJaPdQdYc1D@Ddv8_j zP^URlk5@!8&Y7pY44(UBeCT{)1p?vC`phAqFUi#sz?g3Ei>T-!G^I__fD2eB zZk-FwX4R|GM$A3Yig1Oz+O6)b$len|_?oD2BTtbXW(+;X9BAnT*cvCGaW~Et9%t+6 z_b9gxCe-~+NuAb8Pw?{hE7%c9ww~LD=QFv!gI&4L*i4C{%{~YxBO&&1%TOTUcZnb{ zHAh=3`^z)vH@-2eeA*`2D$2u_#^tpJcF=d zk9Yz>ar=)-TFz6YeT7(2Op5$LZ5Rv6j;gI&fs>(QS>TEJa}!lWB}ov#KNR0N$cZy%n?>m(XjyeskV=_ z6$A{LW8|iYDAOlRRSXc_QfsVf&HbGY%+1;-%J=88wpB%dgEq904L}x*@`b)s04B6H zPC%)??yD9wFud5`;FI}W@<6O5CftDgTWKoDW&z$q0ac}20qQB-;{T28z&}(S#AEdi zo?`GE$s*YT#bK!K{5mi1a*?Sr$}=A@jI4ThhNVY3M_Rd#g~> zxEN!?!;8L=E9Q9+&k8L*#T~`gEE>m=fzl8R-zeECbbrSS z5_iLg_PMizWY|UsuI^{)G-Ux}t1olcQh2EIKB_A_nZg|gc8_1w(>u>O0Ha5%V4;W% zax@So!bj`<9nb#0*-$22Xgsd9eK+|;5usyu;3@%ue>^1SuTV^Q;qh$;V=cnB9g;}h z+vBeik0jTFT+M#8=6G>a6dTz;w>kKQ=W}$d z!YiMPit9mGWYq3NY=wAXnqo!4<{z#4u8tuw2EsDJVi!#X`Zbs^<)6s(I+7Z(FK*!q z^or!)OJ+y^n-l(km+7QrK4c8ETpI60&xa+Eqg{yt{yI(+{+WP4o5eMknTfoR7%@P5 zMDFL`^(yKYFC=5H{#V&|S#KDRR6C8+^hw-{U8Wfwk38)Ew~pu2t|~ed_qJy5uHXtd zu6@u)fEvb8=z}PCZ8h;pPYwn698DL}q}RIcp|j5wA*=nbX8MSMdb3L>070ka&N>E+ zEi8Ik54M1AThTN;VXAx4j(C#qk4hqP$0_n7jjAcLO}viU%ia)pO*8l^hhiGV6m7Hc z4W3)djvGhISJg80N)cwWGH7(!&+hv+58o9;(ZZ#yw5qIFRG!`YyugXD!sWw9_#Tzw zojU;V8No6a>traaRKE4p9w7rsVf~S*T#$hcF%6)2gj!Y_2maP?$fl-Vtvh&KBfrti zrCgx_26qdaOdt}Y??v-rq^z=N*{-QB(tG?Vu1Fw-C zf2d3G&ET>)A{a?A#p@)7lqz#SPv(Q)8f|QX>a$L*NBAAFAqU+(UEw92Y(_mWDQy3> zpY4sAxN3WkC-G#}$%f%gj*gU)cPDfm7AuU#8UNf8y*F{Wu-!^xw zi*KBZGqfMJWt?CdD&ITtZG@nwT$9c|&%psKRI7l!PL{Vm6uohMta*9_gbL!TM*MBE zDT_CPQ$uX`>TiA$w*DyqsD?!oOdN=fODf<@{n9oR{AFrqW-Zt?i5YG*5~^ROI?DTy zbQ#x#2&U5yS9F|Ozlga0+D}9I@nEa*{i;ZFH`!4JdXIRchF%l#OCb!zMf?pf&b);5 zk48uBCQqo}6SY87*_GtA*8T53w}s`0Tn%83qr8w+i7gwvWd z#Zpn3RCuFhYhVx{XXK`A>WgQjgxo~LCeZYX>+w`Yo!^__yKsDn!b(+@7sgp_hX)~b z|AGBp>ETZ<8U(w#Eedt{oQoFAQ(p_YW6yQc88%I5Fm%tHqs}GI78}n<=V4!Mj0kSb z&O)lfjS;!SOJ5x6(pB<-lJv2>95-Lp#^IL>!%BcbI{Vd6AQJ{DSP{3DG|_z-OMr)k z?oa}ZK{8$BM~mrr7hlgq-R~Ch^RD!NKz4RQK*IlI_SO%G|FV6_hT#C~IJY7+61Y;k zBZVAO$KpQLuxnQ~zIjlj-0T0S+7v(+?RL&nI`fiT18(qLp&Bj`DqY|qT1}N|^H;-6 z+*LxkROo5dgFk?>_FXzktLAcoT97+B@|*|le{4yfe$xBVIm38hd6beVdL}u3-9MdM zpzUEhTsP_AnMAv5@0^0&K&vsn+pIFK0?T9TBjBQ;Gi7?if3)0gsTv1~qBOO9eU9dn zlU{+>a;MiqQ3v8UK>A<^qm_z;EBfS&LOqa=#EEeM-TH@*jIV(|bOX@;34(&))W}ip zpv6l$KnIN!l|AvL_|8RX?W9p*;4j^K7NImai(IxqKvNq4hE?zbab|!Y-fR>OaG1DW zXLsFI*?_g^F1~l>=5nM0)O=@RLo71a(O_?kqREuXs;(B^k{7op ztA=eOP)2N!Ma06JP#BfR&Sjh&|2#0y5qD*iQP^Ea5<`19Ugm#4PCj7^m7SD6FXCb~ za<;}d|Kw2RI^-KSd_VT{`GC3G?j_r z3zUxX{qFQ;ZeXgMe$nD=3JQLMbz}tuvcj(7T9AsgcpFu zI7ummSBW+N6pQn{zk`G8i>r>;k%bLs713psLu)x;;GwKk8~!x14nzLR)gq(c8I&3F zC@Anzi=%!L+nIEtTt^F5@fs_NG@xHo$p%U1ozo^-Oyd$gfc*1P+hy!IQ*iS~P-C0W z0|1SL-zP640T4ec;oy+im5pzt8x|#M1F#YgdLp%xIKd2W@d7UFV|g$Z)snF|ZVft0L?BwUP$Uq?a(_8ZAVq}G$X8C@HnH@ zWbU|aOb$~$*^72(6)7QFA`K*=0#g1ha!^=V~J2&K}5Rr-8U7ctT zIL{?T7QAmF0|Dop=FJU-HKOv|A;l%02o0Aupf8$Wd^@iC))2V{ULtMsr;|TyEmB#! zE!bv@MQvhI0+D4VSg6~R&{A?+ymwP}AfvM$?atB!kr2Hm zd9Nm1ONp1!1trI{{(r7>mR&lw@&}JK%_q#mY~eU4rj2rSKW=!JURl6w#9(xXlvN~1 z++*JOKcV_cq=0zyR-bn?t!t*DrHv#ejS&W*O)`^Lb!Gzc`-Qr1#nM+uwoN?PnkTF0 z22&Oop1*x6V-znw);6;_MY4Wx zk>%^Qxd~JIPDa3a%)a^pCfgw=y;*pQaqi-SZ4KvJ^`ao*9m&WMNwOM~m?ZSEnR~@o zxZrrSCS_f}=5JZ!f1Ou&k?_+D11E<#>n~y=J^`~YA2}|@8|E`>a4Z+H2XoEP&|z~# zKQp)`EcXqYe z(Hg!?6OZDjZY`Yj<+-(FGLJNBhBPD+UG*%N5JvAKurq45ca(}A6(FEe`>sMx)T&=_ z;E`gzmRw@t3N%b~qQHk^E5XBELPxmvmnC{wU#AS3w~0wWHHBfsC7$hz^Aaj|Uj)V? z!-UfsA3?7j`7W}3P`I(U^;4*R{j8#4@)!0Fx0=Tr!fLRV*%QUAMrLFiu}mMNspHsGj}axctGc_f{2fFxTH5fH=fPz095V7b1mXU$1ro>t3n zQ-d4$2x*Q)61$z}3}6tvF((JZz)F;eE*&{O0{)Y}Sgemes{@e~W!YkB;+a4u z@X1lcP`@qu#);w^gv^^E@(k6oE)@5uFIFC&Ll4*S@>gKgD`5?&pyvt(DiZ_@__ytF(QFxY6%b}5>eUXH-{+#5I6s{)Nloi5aJSCa+yWB z5(9hJepxjy&ifY$Yv_Ayc0QB+e5#1b>&!zVg}9rReezJAAeehAKC!yP*2e-|ZN=Y_ zE{pe;4T^->4g#R1vT-2Q{Qg_D@rSr~q5*)Aw22}rs5&t6zqr0mKnmgQao?zECb*`e z=i7;54Tr+Iy-wcK2a;r-O6cmS7&4X(GCQ5w_h7|7mms<+zD#sHw~iH&F+L~=;C(@aux1kMDpEOF z_KsGC>&y@Mf*Vl+N(euKvgH(r6?{OQdxC8>l25doCGN{V_SL?d)uQL%QvuVJ@ zWgQJo+D&C_uzjlV6S0e=@5En~L9hIs?As;qNfcxds;Tv*%qM!L zk*Un>4Nfu_tbEhBMf^!+b|jxM$rd%m=g|nVqp~0~v#h=loz3oPE%K^=097AScc4)X zH}FY|jdZb$jP7Z*NQik)LN*ge(!oRhs4lw9kS}KT44+*x$8WZFLKj)CC!K|yg_&$8RNj|$*`3 zIRFJZg0I-R_LMy3YzEFBI)7zVHHQ!w>G*{2A#dTECp<*Ub}8CfrOa`E7VY$V*(C*s z21;8BxN+7Qm=6^T%RLxUVp1Kn>3!#0slaaU&uHSn&dh&>qdn=dpXIyF|BLMZk?YzRIY8dI_$ z!TQ=ilI7}@Xf_k``?j9;gPu$@swavd73Yob8gbtY2(>Yh9KJon0TA_#^4bW zEug29QR&jfoTLr4NDzd}0+djr?@MQI2I8pGIU0Zo2}Pq&%kR^h%_9C4yZQrITgmR_ zSG0SX%=}xq-Z}@k1;V)=OzKTA-wXWPXqfk9elV3KpHICi#H;X_(1XJ&)uIdcH1&1$ zGuM;em{a8L8s_l?(D>U7+#J@n)&&^WKIK z&}lZ2j)|4=Y)$CCCy3qKzkAG{;^dWUEbQ@~FnrlJT!*;4QVhNcuWj?BEZojfuY52}2+ZeVWtopo?6gN9t_y&rWi^s%_Wdv?Sw zdt@gr(;Ak{)FSEvb7i`S`<%A1-LJ(A8NE{JTxhD9#>{7ot~MWpK7*tCWy_g^g*i>N z3({Rv52i8gut$JXn>?VC=luTuCr^(kX0RU<`;}|R03#n8%k#+3%#Rp>d;K*gz<(09 z6qikHe_(ujJD!R^#UI0SHCfUltDJ2T+LD?AWaQ?MqxwM2r^*zZTS#;_yOGsG))EPn za-7W(w>V5qLA~6 zm$3wb;yXIJeT!wpb*kmeQFIoC4*Bw*i++%N04y@h@YuYSgl-V%I4RSoC|XFYjiGlo z?m`&;jm%6funrd`P#=j^^HEPdbRYp&$XI%|{9|N+hKOSX%*Ce5U=%@#IFIjaq;9(> zxi=M@zs23@9{xD18!$ij43i2|uE<2)YaMRT@jg>qbXd8xG=E9@Zbo_D0aYWa+zFT| zYws@XbZ~CB3mU2AxOksUzS8s5Wa?uEoWM@WSNli+KIEiPTES6FKi|q*b2wBYhb|Me!M9}+D4r;k{Cva&~*UA*Xb^Xp46%{6y&6(==POy_L z+~YPMD$nlMWos$CBd8S`+!@EnPw6kak!R&ksa)zpYlP;rg%J%6Cnm+7A+2|>U0C=Z zyw!TLZ|81`{wkAC!~zG=HgkK<_`Fq1eH;?ds8XSPbWbJG6aWT0(Zd>39kvH={R`Bt zZkKfhw}6x7>L-sa_U&HLFeym`xrQqWOJRVyp;Ycda=g^JzxDnKT5{k~8R&$XmuWrI z90CMThhk?08oBS^z|=qt8kBQe8yZaIW#UYt?vE&r_%qG~hQnjSP~Y5ye_Jv#8WOzn z>aoYJCU*hWaxA9AJ*7-}5`cp;IT{PA9V?8o@uNfWTk8ieAA;hcH3qWEdeqnO0}1&_Tv#g`1%MW=52 zL2nfnY1}|X47%%~oG{{v)#bp^yvdsJ;wq?dqA)2%g_O*HT~~lAw7?Jc%7N|v=K$1Y z6)T2X8C?NKVWjiHfTVr<74Pa!ABC={g+Kz2)%co|QH%u7rswh=bpeYu!#hWKEnCnr z0(PsF04*3xmA9R18MObI4fj?Qh5We3rP+-q*B@)s>^Xssvwze?70TzVcuB)@c3|{{ zdvq)7?zb0pq+8U7+{w!~HDo4fdgOqT`=`yq=1XH2jBQ^BK}*R`In*y*F3oVYE%3@@flD^i4*0*c=c*kXAP4jWg2 z{g%}R{Ew&7js6_67p^e?K$Ij-`3cx(=6rhs2>X%!j z#1nkZT$Nr{zX|D-zs{wliZsv!l6w96Ib>E9+L@aBQk2=a|HMZ9A6AWp!k}PMf-yJR z##0*g##0h`#}UAgcijPl8d-D-3kDIVPw!n@cC1*zRIsZ8r}+lnMTq4gQP67QDcP2_ z8?m9f8*#OcZwNNljPow_#YB^t-w9CpEjz~%y|3!DLUHx8#OgGd<&;`+D699Q%?5!s zDB%-M$-&O>cBSWcMBLDhfP@0g>uyrWy>X&+1H z7-M~oeLWI&BqM_`MmgWJ>7+uwubP|!q78WR)0P{cHBm{?cYBUm>ey%02$g7$e^`i( zm|_v#$(Hh{cgGt52y69p&$DHu9W6q5ZgHnNHniSd_=JJX$u6;Pifn)5T{*#%2gK^=1lMYD|^<1673Ds)q9VDR=iiBq)J!D`q z5|n-KmqF)W3=Vn>WaDQ{)Fg^Z)wn;t1pl>Oe>mc+{??U|1qt|dB6cI#q(x^ za@}hQ=j59=`&_lLVJqfE(J2^1u=r*GDFeRM3cY8 zJ?H(t*xobEEc0tjym)uE6WlUd$3i8b0UBK5VAnM?tMBXs1SqLnZf^ zdK)Ifn>M&`u;`z&xzC8gq`8;oXOs^!TG+?UKp;Gb2cy~=m!RD*t=hvx_>Boui@h!G zhp@x7h>pmoWth2^$a&P`v*E34=cpH>w1LYnlVqUu2##Z5noF#1g^l^%VFPkLQIV#^ zj?P^do+ma9WdKZbm18+ya28-*w#)ht z`K>_PJBK~T5=IosK0=vnU>I*$7kmSzNku3LR!B3y!n907h}T547&&1_{duJX~>3;Zy~?z|>3HtEK9?9$6F207QMp+8dF- zJeYMg(ms;JYNfTft?{=dVR{Urfbzm9G{i2cw)|F#aT$X6u+jTgQt0kHo6i>T@+BLj z_69V~;~#3}ezYCbS5%Ic3(pP>%O4t|AhoQJg@Dmz_F_wXA_d)}?gmE|9fiiMB-S}S zKr7{qFG;NhB7e{;QW2s`lZP7G5BuAQSa`W~XFAteMl4r-)NQMs^G7`Jw!)lxkEerZ=mEy8R-0O%7G*vq4cQwVp=H`cCb-lB<0hE0M}yHeOTtDA<}h>8C|LE;~JtP(1z_9~TM4Qd-ukt^Ld{Q?r!O7eZ^r{f|c3=6>*O6odQT{!m@Qo-- zI>1Pa`jXUySM0Wp+v|X^s2x}J)mnk)N6(&$1*^3eU>2)hlXIOhx8K3WnhP`1)o+R> zJ~ROc_VFq0`a`80+*jti8@(Bj!16FjjNNr?vbmLS@i%@3Ta|2`kY7W|nGj~;bDQxt zD7c~_VTz`(Wvr>TS?}0@Gh)BBu+G>$QxaG*gvk9c_%aIWY4X->)v{h}T*}S$qJ_*( z(GIG}$mP&M+cWKMVNSx*k9%RZS;y@>|JYTS|K`Jt2aIw| z2J4vT0PuRu;I6^cTO{(jmJjqV!rlkjZ5vJkF`}5f zPRZ2`22H#_(1nO4OpDaW?M&!rdgT}S{=z?77?;){ubPB;WC1+3q`9bn(=z+7;+_f> z{?n95lvQvz^Ort5VIrC8TdchcFBbld^t05#4PnxZ1A*g+odd}j#dIGM6gem!LshZn zC^$-sGM@}qC!H!hCpkehIc4@ofIv%A9k3*1H!F{fS{!L!Jy7M{T$nUy-xvQL2oC?` zt`71rwbc=fC4tRvhU!fUAlZ ze?i*O> zpW$buk8F!>m>|*z*F{G#ZGp7|5T?}$@S!u8qd{C#^A8=?4VHumYp8fx0D0_>)!`u_ zlh2V?wg{vvn@p8R9o3-G67Nm7VKa#wAB|X-<_io-+XO`u4dwp+B+;*b2+p?q*&^qd ziweswE^zSitre~UeTMjFPv){j_fDL^2B6C{X!id+bZ607gC|F8@}>)=_uJNMw2xW% z>b!3|yO`P!gzD(NgB$X+Bq-LwrQsBp=1<^?04Sda1!fWU_#hN}P=4e`1+#d9RC~U* zl&*LX>d`f&Fv-PhC$%x^yr|=oVURZh(Dj$#EV`+Tz)C9&U2+UHX#_?Z(VlCRe@iNW z7T`{1o07Si1uG&7DT`fR4CUdr&!C*gU;!XV*x`hYB4Gy1Y$rROp6?U5L4S`8JwoL}dL&j)uApZp$8wcOt=W9x<`R4YSLRj@X z``yI?tr@a4a31CC#tNsUuFtniD;j4w#^Jd4K!IGb%14r)fB2#dW6^(;=;v%pxz_U{M)sz z+@flymLIraCK<&Jc9-OoCGh|33o;3_%ZiKE0B zwmGPSJyaovpG%iY<~eAHa+RQb(XvZ}`GG$aVrQZaUGxhkh;g1c#3arR`2JSJl3Vm) z5ZXJc>QUdrjpS?%y@P8#_&5p@&(&IS;jTPwQe?c8<{?iOui*`*ouv01Klrj@J0iE~ zQx!y21Rl3(sz#IwZXIOYLvxr!Wr^YFE=F^O)b_wXW`{*3paO2?^%-jGT`K7neiDgC zSnJdh31!Q+00TBrQ8ulJfL?J(kN?*q!aHh3WfMe#lEsr9O91p6h-7qiP|M?>=3> zmH8N1EdXis^g}r_SRx=b;5o(YTjLx+nqtCcfy<}GVwpLNi^0kB&$+>CRaszReI|$s z7yPXI=J%*BE%7@^8M=o@*Wv8gbH~53@`sP4Ray@$9V-!S(IKkDlSiss;CSni_GGmP z>fH|{VCX?(<4saiF}Lp1#J8?+%_K(jQ;rJ=(WI;;PZ$cA!x@imL&_49w;_HSA5)MI z`)I2}Or)KDrhp@K(yxo_z^<0YXS8#qCqE^* z19Rx}cAxbx{UD4f3R-?VJ4}a-> z*W6@bz$Y~ef5PY7k@yy)B&PI8u8Yr7f2l7NV5?-!@jIxLMh4+b3g=Q_Ej>IBo-o7IM| zY0A5dC!u+gUkt>?xhl3N&t94lG2e_2b8l`oUZR>Qd6a3#0luAYo~3wfL8%i^`^FcN zmqN+EkIFS-f!s?KHn#4WJfJ~{Q-Z0veh?xZG@Rh2dDLl+(?mI1YF=ij@kUXQHGdrE z4{0yjeK$n$qYhGbmntTZ05hXp4^MwXXX)@fkh$1o&EjOkAVarA)%IDo zu~l8W$1zm(fhH^sx?t;~NP|d0=EEnjptjK_3|e}h=%%@x^sTb}1u&ZcA9z?nW2M}vEzx-sDa z^e8u=xL)Y2d~i_VOlV&Ep(8Q034AIb4kx0uA(ml~Tss4G@p3T|}2EPVTEMOpUf{ zvV;dlZ!mu0p-+p|Hf&Za)T&!R*&L8=a_cA~g;`815ui{ae{)IV9MctPx^$^MWxW(s zdvD_Q5KQHPyUCJV{4Bn9Bu5sDRN@qySazP;xkxuBU$s}Nx)xkJ`rJ!u2bNIoIhZ*k zCu`}KXjW@SClF*O4PT_M~LCEC_b+ zl#a$S{JQr5Kl#l_wg;%r1V)^AcVqfOsyRzkXg?ZN^#rDWZ$mPkd4Icf#!ci7i;V+g zJ88?Jhn{$m0HkfJdOas4%n7UsP=e}vSz%oUO^FH31f89X6aTrOFl#g`Wd#`YqtlF< zFakIu0IknXhB7`%;5rJCTM3g$I%nE#P_Nd9zv#$rV0_Y8hUp}jz;&EA{9?$%r5RVj zCgLF(%+O4>rv0{`7)QEQ0KmVKS%47CYfW0CcR9%Mzi4%FSqm3ZL+-6^coeZRGm;@1 zwJCZs_0l?OH(~q#Qjo43XUDJ< zX9>c?F5zr>>T*=eRqo=3=p(q>EO z!kB&RFJ}JA9{?}u3jwF@$_nvgKNjD>)gtXL04zPVyHT#!Nal`t8VjFaw-t$5MMkMy z?Ty?oYm{?R+e^xmv#g&StLg#aX~~RPjkVdIu6$%6I?^rDL?y1N$RBbw*i4>s3x;Xm z2_vKWLbg`qL4ZxEcYyeIvc3ydDasjF9r{3E{mrqJ$0jxD#1I1^Z&OVYcn5C1ET*w@F> zO+*^NLy-=2&AsQah4b3Ux7dIx%QcCR;{Y8S_U)WFykPe9uZOV}gz$n3R?@>`Yy%eW zk?AQRBcsnjG&94uB)7FN)Q7J*!@hyulw~LRWE2vtYdjDeDD)cMYL@$&^ZSOKfH6tS zB1cyIdDQ?47RGNwJJ9wnGfA&LGtXkKU}x}WFwOSn!Z1bA9vfe8??h$u`7} zuLL}=J3+j$mML_yX3Ph8FueakcM^nhJKH&><%cN9aF++=h{`rQC|npY_X#*gNI-&) z`x@XbRgXa5(=Yf)^Pc9k$Gla;`E#iq_#d&Dm|ZIU+aZ-1X$VmTt3t*g`MXc7K##-0 zQ3Q2UOELhrd}ABWB*Wm<1)IKxK5Jmc?(DOM=-^%9eW|=ta^So1KYw6~OEV`KBim>P zK~LhPc2NeGw5Cs5rf1wsxg8t%Q%U0UeeRx~ig|AR7U}`Nw2JS$fOLr?nAa50Z^|>M*Ozj(dH<`{D)g&ce zy`%h6duExK{J}hVE>mBq1M-KROL656^J9AD|{8W2m@s#^#73tqL}IyCOcRFZ7pwKrJiV0npB{mOO`OM5r55f{aj zQlA15*x3A;^k?!^xaD#{a1Af0Y_2l~MNgcAwmD0SF>ZQTO6{vIsfU4W|;@DJ-%escwi$~DiQ1WElxZPqO%hzC{h1A^E|G`r}5 zh0jPJnqA?NK>F_ABnDHB3$j)A9VMY|`L~U$k@E(1AKnq=GV~0cQB`ZaYYyTmdK%Oi zVVtJ&vY(J*`g8GIKEZH67PejTV1tWVxU@D`r-*N#?agWcQ zWRO3^mqnVLjoK*kpv_^4YjCUdAuWzLiK^EPOc)%LqbQ?(wA*MSozkbTj$X~}huM}< zNI!f4$hw8xX5&_qH>WM{Ca$9+{ha8jQ3#d|mpq-=YIxwl_Z4Z!CDg9EC36<~50SPF z?MHOi(U8uvWn0M0!NFP&z1H&iE&tvmL=e3vZJhC@L|E;aL=N%goMg-Z!NdR@9MlOc!fZeq1V}mJL{#C#XI;LGvCLIfia1p6OPnovc)2y zjC{LvFQGE{(Jp-&ik{Hje|De3NL2TN2$B9wjqM!-S#KjbxnHeN>fXTp|FlWDQg8`$ zn|Zc#+~FpcX+Z5Y`)dujccW7bPA3g?SAnE$G*~h@7X@Alq5GX?c31n6<9c{?f~VMZ z0O!iKG=Ef2II8|@!QyOXw6l+IoTi$Dz3C?&(JPmch75q`Z`a*YR`I~ht-hgN@VU+u zrelQoM$d3HG1!D+BnMBw2-^3*JVaRJxLOvvubk=2j**h3811jo9Y66GFpL@o=cm^L zPQo5^y=<_xC%8YX8@ayf9+mKds)3aKj+Aj9J&z=G2gCo1xp?q5H z(Uq8rekzgG$~##M8F5*0vX#kxDv|GfPOzW<#R4J#>~mA6{l2H1_|U0mGG!taf*e2M zh{972oW5fT0&x5U6y)5Uzy_E%>m|pvkwe1UcELWjGMeW^E4f@oa&MZQA)-+;C#RU{ zy*_wiA9T$gTOdOD-BUg3fQ$&VUJ+%A4F||z!x;i+2UbN73RkMR-jpiJ6nr|kRLh9B zmRGE{P2p)A?yR>}*SJ?9{>4=xq|tP;hkkKJa3rT4YNST|B*|K2vZ_M-0)C5vr|V7z?I$*1=WWaah-m!lGKlXG-c<02 zbu2L3!cbI^B9{gOU&(Mg6u>e!n57i~XK~cQ3vBw2k>SvKux=NWan}V3J{5qNs({NX zY~Qsz@SbaOD{YXKI`MDmk0sgucWblht6ADBM=}c)fQ>=B&OJ$5e(;R@qNQa49&yX* zLG+z;*YOf3zE_%6{e46~mA6{l2bzF&dw@iH*1eyp4K!iv34=TTueqUx)wg1qe~wml$twjzCs!O%KN1o&DA&cv<)aE{LcRbELb!JnhpJbJ-wg!+^V= zjKqvP5&x`}s8i4t!zW8AmWvu;zb+WPOlEW3Ts7psDmjpz00sv1V6H2wEIG(FR_)N~ zCi}w`3SE2=*ZZjfux}7&#Wd2sq=eMpbybV5+O60BHPD*?y4M$bB!-&n)Es%lYoEOW z=M~Ck^5<$dsLyn*ub?~ad6w@Z?BWTd=Zu8>G>k=(bIENZvYX0_COIXG*nnjl5wi3g z2PrKgK&FfS#5`@fX@1dUPD}zMTeB8;&!oVVjgEP0J2!vh<+jWZ01F0bi>7rQ8uq4- z(Rkw-%wv7Uy}t;?^nOfCVBZ8e(~p0`sWy8n?D$BDCiaxC_Zydj1PYcT(OQ9uI#00001Sz04psS^MI literal 0 HcmV?d00001 diff --git a/inst/WORDLIST b/inst/WORDLIST index c725269d..95f33521 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -29,14 +29,25 @@ SPR Spawners StockSmart Subseason +aa +agedbase alttext asar assmt +BAM bam +Beringraja +bigskate +binoculata birthseas +Birthseason +Bizzarro +BMSY +caal cha cmd conjuction +CPUE csv customizations cv @@ -44,39 +55,76 @@ dat dataframe's diagonostic dplyr +EffN facetting +FIMS fims flextable +FLIMIT +Fmort +FMSY +FTARGET +Gertseva getwd ggplot gp +Hennen hline href +Ianelli is’ +Jeffreys kableExtra kobe len +lencomp +lendbase +Lifecycle +loess +McAllister +mis +Modelling +MSY msy natively +NOAA noaa +Nsamp nsim +obsvpred +ORCID +pearson +Petrale pkgdown +Pre +Preformatted preformatted +qq +Rceattle rceattle +Rda rda rda's rdat +Rmarkdown +Rmd rowid satf sd +SDNR se seealso socio +Spawners +SPR ss +SSMethod sso stddev +StockSmart str stringr +Subseason subseason tbl theming @@ -86,5 +134,7 @@ tibbles tidyr tidyselect unfished +WCGBTS +Wolfowitz yaml yml diff --git a/man/bigskate_caal.Rd b/man/bigskate_caal.Rd new file mode 100644 index 00000000..9d165309 --- /dev/null +++ b/man/bigskate_caal.Rd @@ -0,0 +1,43 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data.R +\docType{data} +\name{bigskate_caal} +\alias{bigskate_caal} +\title{Big Skate conditional age-at-length data} +\format{ +A tibble in the standard \code{\link[=convert_output]{convert_output()}} format with 26240 rows +and 35 columns. The columns used for compositions are: +\describe{ +\item{label}{"conditional_age_composition_observed", +"conditional_age_composition_predicted", +"conditional_age_composition_pearson_residual", or +"conditional_age_composition_sample_size"} +\item{estimate}{Value of the label} +\item{module_name}{\code{"condbase"}} +\item{year}{Year of the observation} +\item{fleet}{Fleet name: "Fishery_current" or "WCGBTS"} +\item{sex}{"female" or "male"} +\item{part}{Partition: 0 = whole catch, 1 = discard, 2 = retained} +\item{length_bins}{Length bin the ages are conditioned on (\code{Lbin_lo} in r4ss)} +\item{age}{Age} +} +} +\source{ +Taylor, I.G., Gertseva, V., Stephens, A., and Bizzarro, J. (2019). Status of +Big Skate (Beringraja binoculata) off the U.S. Pacific Coast in 2019. +Pacific Fishery Management Council, Portland, OR. +} +\usage{ +bigskate_caal +} +\description{ +Conditional age-at-length data and fits from the 2019 Big Skate stock +assessment (see \link{bigskate_lencomp}). The data are the \code{condbase} rows of +\code{\link[=convert_output]{convert_output()}} applied to the output of \code{r4ss::SS_output()}. Each age +composition is for one sex and one length bin, and sums to one. See +\code{data-raw/bigskate_comps.R}. +} +\seealso{ +\link{bigskate_lencomp}, \code{\link[=prepare_composition_data]{prepare_composition_data()}}, \code{\link[=plot_composition_andre]{plot_composition_andre()}} +} +\keyword{datasets} diff --git a/man/bigskate_lencomp.Rd b/man/bigskate_lencomp.Rd new file mode 100644 index 00000000..64e8ef0a --- /dev/null +++ b/man/bigskate_lencomp.Rd @@ -0,0 +1,48 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data.R +\docType{data} +\name{bigskate_lencomp} +\alias{bigskate_lencomp} +\title{Big Skate length composition data} +\format{ +A tibble in the standard \code{\link[=convert_output]{convert_output()}} format with 14504 rows +and 35 columns. The columns used for compositions are: +\describe{ +\item{label}{"length_composition_observed", "length_composition_predicted", +"length_composition_pearson_residual" (as reported by Stock Synthesis), +or "length_composition_sample_size" (adjusted input sample size, +\code{Nsamp_adj} in r4ss)} +\item{estimate}{Value of the label} +\item{module_name}{\code{"lendbase"}} +\item{year}{Year of the observation} +\item{fleet}{Fleet name: "Fishery_current", "WCGBTS", or "Triennial"} +\item{sex}{"female" or "male"} +\item{part}{Partition: 0 = whole catch, 1 = discard, 2 = retained} +\item{length_bins}{Lower edge of the length bin (cm)} +} +} +\source{ +Taylor, I.G., Gertseva, V., Stephens, A., and Bizzarro, J. (2019). Status of +Big Skate (Beringraja binoculata) off the U.S. Pacific Coast in 2019. +Pacific Fishery Management Council, Portland, OR. +} +\usage{ +bigskate_lencomp +} +\description{ +Length compositions and fits from the 2019 Big Skate stock assessment for +the U.S. West Coast (\code{BigSkate_2019} in the Stock Synthesis test models), +the model used for the r4ss example plots at +\url{https://r4ss.github.io/r4ss/BigSkate/}. The data are the \code{lendbase} +rows of \code{\link[=convert_output]{convert_output()}} applied to the output of \code{r4ss::SS_output()}. +See \code{data-raw/bigskate_comps.R}. +} +\details{ +Compositions are sex-specific, with female and male proportions summing to +one within each year, fleet, and partition. \code{Fishery_current} has separate +retained and discarded compositions. +} +\seealso{ +\link{bigskate_caal}, \code{\link[=prepare_composition_data]{prepare_composition_data()}}, \code{\link[=plot_composition_distribution]{plot_composition_distribution()}} +} +\keyword{datasets} diff --git a/man/plot_composition_andre.Rd b/man/plot_composition_andre.Rd new file mode 100644 index 00000000..8ac08979 --- /dev/null +++ b/man/plot_composition_andre.Rd @@ -0,0 +1,84 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot_composition_diagnostics.R +\name{plot_composition_andre} +\alias{plot_composition_andre} +\title{Plot Andre Punt's mean age at length for conditional age-at-length data} +\usage{ +plot_composition_andre( + dat, + interval = 0.9, + flip = FALSE, + fleet = NULL, + ncol = NULL +) +} +\arguments{ +\item{dat}{A tibble returned from \code{\link[=convert_output]{convert_output()}} that contains +conditional age-at-length data (see \link{bigskate_caal}), or conditional data +in any other format accepted by \code{\link[=prepare_composition_data]{prepare_composition_data()}} with +\code{conditional = TRUE}.} + +\item{interval}{Number. Coverage of the intervals around the observed +values. Default: 0.90, corresponding to 90\% intervals} + +\item{flip}{Logical. TRUE/FALSE; put age (the mean age or its standard +error) on the horizontal axis and length on the vertical axis, as in a +growth curve. The default (FALSE) puts length on the horizontal axis, +as in r4ss.} + +\item{fleet}{Character or numeric vector. Fleet(s) to keep, matching +either the fleet name (e.g., "Fishery_current") or the fleet name with its +partition (e.g., "Fishery_current (retained)"). NULL keeps all fleets. Default: NULL} + +\item{ncol}{Number. Number of facet columns. NULL lets \code{ggplot2} choose. Default: NULL} +} +\value{ +A \code{ggplot2} object with one row of panels per year (and per sex +and fleet when there is more than one): mean age at length on the left and +standard error of the mean age on the right. +} +\description{ +For conditional age-at-length data, plot the observed and expected mean +age in each length bin, and the standard error of the mean age, for each +year. These are "Andre's plots" from \code{r4ss::SSplotComps()} (subplot 12). +} +\details{ +Within each year and length bin, the observed and expected mean +ages are \eqn{\sum_a a\, o_a} and \eqn{\sum_a a\, e_a}, and the standard +errors are the corresponding standard deviations of age divided by +\eqn{\sqrt{N}}, where \eqn{N} is the number of fish aged in the length +bin. Following r4ss, the interval for the mean is the observed mean plus or +minus the normal quantile times the observed standard error, and the +interval for the standard error is based on the chi-square distribution. +Observed values outside the intervals, or expected values that +consistently differ from the observed values, suggest mis-specified growth +or variability in length at age. + +The statistics are calculated separately for each sex. Unlike r4ss, which +combines sexes, this avoids jumps in the expected values when the ratio of +sample sizes varies between sexes. Length bins with no observations or no +sample size are omitted. Plot one fleet at a time with \code{fleet}, and subset +years by filtering \code{dat}. +} +\section{Equivalent plots in other packages}{ + +r4ss: \code{r4ss::SSplotComps()} subplot 12 (Andre's plots of +mean age and standard deviation for conditional age-at-length data). +} + +\examples{ +plot_composition_andre( + dplyr::filter(bigskate_caal, year \%in\% 2016:2018), + fleet = "WCGBTS" +) + +# Age on the horizontal axis +plot_composition_andre( + dplyr::filter(bigskate_caal, year \%in\% 2016:2018), + fleet = "WCGBTS", + flip = TRUE +) +} +\seealso{ +\code{\link[=prepare_composition_data]{prepare_composition_data()}}, \code{\link[=plot_composition_bubble]{plot_composition_bubble()}} +} diff --git a/man/plot_composition_bubble.Rd b/man/plot_composition_bubble.Rd new file mode 100644 index 00000000..563ffb45 --- /dev/null +++ b/man/plot_composition_bubble.Rd @@ -0,0 +1,87 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot_composition.R +\name{plot_composition_bubble} +\alias{plot_composition_bubble} +\title{Plot composition bubble plots} +\usage{ +plot_composition_bubble( + dat, + value = c("observed", "pearson", "expected"), + comp_type = NULL, + fleet = NULL, + max_size = 6, + ncol = NULL, + direction = "v", + conditional = FALSE +) +} +\arguments{ +\item{dat}{A tibble returned from \code{\link[=convert_output]{convert_output()}} that contains +composition data (see \link{bigskate_lencomp}), or composition data in any other +format accepted by \code{\link[=prepare_composition_data]{prepare_composition_data()}}.} + +\item{value}{String. Value to plot. Options: "observed", "expected", or +"pearson". Default: "observed"} + +\item{comp_type}{String. Type of composition to keep when \code{dat} contains +more than one. Options: "age", "length", or NULL (keep all). Default: NULL} + +\item{fleet}{Character or numeric vector. Fleet(s) to keep, matching +either the fleet name (e.g., "Fishery_current") or the fleet name with its +partition (e.g., "Fishery_current (retained)"). NULL keeps all fleets. Default: NULL} + +\item{max_size}{Number. Size of the largest bubble. Default: 6} + +\item{ncol}{Number. Number of facet columns. NULL lets \code{ggplot2} choose. Default: NULL} + +\item{direction}{String. Order of the panels: "v" fills panels down each column +and then across, so consecutive years are stacked vertically and cohorts +are easier to follow from panel to panel; "h" fills panels across each row. + +Default: "v"} + +\item{conditional}{Logical. TRUE/FALSE; plot conditional age-at-length +data (see \code{\link[=prepare_composition_data]{prepare_composition_data()}}), with age on the y-axis, length bin on +the x-axis, and one panel per year. Default: FALSE} +} +\value{ +A \code{ggplot2} object. +} +\description{ +Bubble plot of compositions by year (x-axis) and bin (y-axis). Bubbles can +show the observed (or expected) proportions, which makes strong and weak +cohorts easy to follow through time, or the Pearson residuals, which show +blocks or stripes of misfit among years or bins. +} +\details{ +Panels are created for each fleet, and for each sex when +compositions are sex-specific. For Pearson residuals, filled bubbles are +positive residuals (observed > expected) and open bubbles are negative +residuals (observed < expected), with bubble area proportional to the +absolute value of the residual. The maximum absolute residual for each +panel is included in the panel label. +} +\section{Equivalent plots in other packages}{ + +r4ss: \code{r4ss::SSplotComps()} subplot 2 (bubble plot of +observations or residuals) and subplot 3 (conditional age-at-length). +} + +\examples{ +# Observed proportions +plot_composition_bubble(bigskate_lencomp) + +# Pearson residuals +plot_composition_bubble(bigskate_lencomp, value = "pearson") + +# Residuals for conditional age-at-length data +plot_composition_bubble( + dplyr::filter(bigskate_caal, year \%in\% 2016:2018), + value = "pearson", + fleet = "WCGBTS", + conditional = TRUE +) +} +\seealso{ +\code{\link[=prepare_composition_data]{prepare_composition_data()}}, \code{\link[=plot_composition_residuals]{plot_composition_residuals()}}, \code{\link[=plot_composition_distribution]{plot_composition_distribution()}} +} diff --git a/man/plot_composition_distribution.Rd b/man/plot_composition_distribution.Rd new file mode 100644 index 00000000..6170c2c8 --- /dev/null +++ b/man/plot_composition_distribution.Rd @@ -0,0 +1,85 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot_composition.R +\name{plot_composition_distribution} +\alias{plot_composition_distribution} +\title{Plot composition distributions} +\usage{ +plot_composition_distribution( + dat, + show_expected = TRUE, + aggregate = FALSE, + comp_type = NULL, + fleet = NULL, + ncol = NULL, + direction = "v" +) +} +\arguments{ +\item{dat}{A tibble returned from \code{\link[=convert_output]{convert_output()}} that contains +composition data (see \link{bigskate_lencomp}), or composition data in any other +format accepted by \code{\link[=prepare_composition_data]{prepare_composition_data()}}.} + +\item{show_expected}{Logical. TRUE/FALSE; overlay the expected values as +lines. Ignored if \code{dat} contains no expected values. Default: TRUE} + +\item{aggregate}{Logical. TRUE/FALSE; aggregate compositions across years +within each fleet. Years are weighted by their sample size when sample +sizes are available. Default: FALSE} + +\item{comp_type}{String. Type of composition to keep when \code{dat} contains +more than one. Options: "age", "length", or NULL (keep all). Default: NULL} + +\item{fleet}{Character or numeric vector. Fleet(s) to keep, matching +either the fleet name (e.g., "Fishery_current") or the fleet name with its +partition (e.g., "Fishery_current (retained)"). NULL keeps all fleets. Default: NULL} + +\item{ncol}{Number. Number of facet columns. NULL lets \code{ggplot2} choose. Default: NULL} + +\item{direction}{String. Order of the panels: "v" fills panels down each column +and then across, so consecutive years are stacked vertically and cohorts +are easier to follow from panel to panel; "h" fills panels across each row. + +Default: "v"} +} +\value{ +A \code{ggplot2} object. +} +\description{ +Plot age or length compositions as distributions (proportion by bin), one +panel per year, optionally overlaying the model-expected values. Setting +\code{aggregate = TRUE} instead sums the compositions across years so that each +fleet is summarized in a single panel. +} +\details{ +When compositions are sex-specific (both "female" and "male" +values in the \code{sex} column), females are plotted above zero and males +below zero. Bars show observed proportions and lines show expected +proportions. Females and males are colored with the two ends of +\code{viridis::viridis(2, option = "viridis", end = 0.75)} (\code{"#440154"} and +\code{"#5DC863"}); add \code{ggplot2::scale_fill_manual()} and +\code{ggplot2::scale_color_manual()} to the returned plot to use other colors. + +If more than one fleet is plotted without aggregating, panels are +created for every fleet and year combination; use \code{fleet} to focus on one +fleet at a time. +} +\section{Equivalent plots in other packages}{ + +r4ss: \code{r4ss::SSplotComps()} subplot 1 (by year) and +subplot 21 (aggregated across years). +} + +\examples{ +# Observations only +plot_composition_distribution(bigskate_lencomp, fleet = "WCGBTS", show_expected = FALSE) + +# Observed and expected +plot_composition_distribution(bigskate_lencomp, fleet = "Triennial") + +# All fleets aggregated across years +plot_composition_distribution(bigskate_lencomp, aggregate = TRUE) +} +\seealso{ +\code{\link[=prepare_composition_data]{prepare_composition_data()}}, \code{\link[=plot_composition_bubble]{plot_composition_bubble()}}, +\code{\link[=plot_composition_residuals]{plot_composition_residuals()}}, \code{\link[=plot_composition_sexratio]{plot_composition_sexratio()}}, \code{\link[=plot_composition_francis]{plot_composition_francis()}} +} diff --git a/man/plot_composition_francis.Rd b/man/plot_composition_francis.Rd new file mode 100644 index 00000000..df22b8e6 --- /dev/null +++ b/man/plot_composition_francis.Rd @@ -0,0 +1,90 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot_composition.R +\name{plot_composition_francis} +\alias{plot_composition_francis} +\title{Plot mean age or length with Francis (method 8 in Table A1, TA1.8) data weights} +\usage{ +plot_composition_francis( + dat, + interval = 0.95, + show_francis = TRUE, + comp_type = NULL, + fleet = NULL, + ncol = NULL, + direction = "v" +) +} +\arguments{ +\item{dat}{A tibble returned from \code{\link[=convert_output]{convert_output()}} that contains +composition data (see \link{bigskate_lencomp}), or composition data in any other +format accepted by \code{\link[=prepare_composition_data]{prepare_composition_data()}}.} + +\item{interval}{Number. Coverage of the interval around the observed mean. Default: 0.95} + +\item{show_francis}{Logical. TRUE/FALSE; add the Francis TA1.8 weight for +each fleet to the panel label and show thinner, capped intervals +representing sample sizes after applying that weight. Default: TRUE} + +\item{comp_type}{String. Type of composition to keep when \code{dat} contains +more than one. Options: "age", "length", or NULL (keep all). Default: NULL} + +\item{fleet}{Character or numeric vector. Fleet(s) to keep, matching +either the fleet name (e.g., "Fishery_current") or the fleet name with its +partition (e.g., "Fishery_current (retained)"). NULL keeps all fleets. Default: NULL} + +\item{ncol}{Number. Number of facet columns. NULL lets \code{ggplot2} choose. Default: NULL} + +\item{direction}{String. Order of the panels: "v" fills panels down each column +and then across, so consecutive years are stacked vertically and cohorts +are easier to follow from panel to panel; "h" fills panels across each row. + +Default: "v"} +} +\value{ +A \code{ggplot2} object. +} +\description{ +Time series of the mean age or length in the observed and expected +compositions. Developed as a diagnostic for the Francis (2011) data +weighting method (TA1.8), the plot is also a useful summary of trends in +compositions and lack of fit among years. +} +\details{ +For each year and fleet (with sexes combined), the observed mean +is \eqn{\bar{x}_o = \sum_b x_b o_b} and the expected mean is +\eqn{\bar{x}_e = \sum_b x_b e_b}, where \eqn{x_b} is the bin and \eqn{o_b} +and \eqn{e_b} are the observed and expected proportions. Assuming a +multinomial distribution, the standard error of the mean is +\eqn{\sqrt{v_e / N}}, where \eqn{v_e = \sum_b x_b^2 e_b - \bar{x}_e^2} is +the variance of the expected composition and \eqn{N} is the sample size. +Intervals are drawn around the observed means. + +The Francis weight is \eqn{1 / \mathrm{var}(r)} where +\eqn{r = (\bar{x}_o - \bar{x}_e) / SE} are the standardized residuals of +the mean across years. As in Stock Synthesis and +\code{r4ss::SSMethod.TA1.8()}, all partitions of a fleet (e.g., retained and +discarded) share one weight, and its 95\% interval is calculated by +bootstrapping the standardized residuals (1000 resamples), so use +\code{\link[=set.seed]{set.seed()}} for reproducible intervals. The weight assumes a multinomial likelihood and has not been +adapted for Dirichlet-multinomial or other likelihoods. + +Without expected values, only the observed means are plotted. Without +sample sizes, no intervals or weights are shown. +} +\section{Equivalent plots in other packages}{ + +r4ss: \code{r4ss::SSplotComps()} subplots 8 and 9, which +use \code{r4ss::SSMethod.TA1.8()}. +} + +\examples{ +plot_composition_francis(bigskate_lencomp) +} +\references{ +Francis, R.I.C.C. (2011). Data weighting in statistical +fisheries stock assessment models. Canadian Journal of Fisheries and +Aquatic Sciences 68: 1124-1138. +} +\seealso{ +\code{\link[=prepare_composition_data]{prepare_composition_data()}}, \code{\link[=plot_composition_distribution]{plot_composition_distribution()}} +} diff --git a/man/plot_composition_residual_distribution.Rd b/man/plot_composition_residual_distribution.Rd new file mode 100644 index 00000000..3a859a3a --- /dev/null +++ b/man/plot_composition_residual_distribution.Rd @@ -0,0 +1,62 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot_composition_diagnostics.R +\name{plot_composition_residual_distribution} +\alias{plot_composition_residual_distribution} +\title{Plot the distribution of composition residuals} +\usage{ +plot_composition_residual_distribution( + dat, + type = c("qq", "histogram"), + comp_type = NULL, + fleet = NULL, + ncol = NULL, + direction = "v" +) +} +\arguments{ +\item{dat}{A tibble returned from \code{\link[=convert_output]{convert_output()}} that contains +composition data (see \link{bigskate_lencomp}), or composition data in any other +format accepted by \code{\link[=prepare_composition_data]{prepare_composition_data()}}.} + +\item{type}{String. Type of plot. Options: "qq" (normal quantile-quantile +plot) or "histogram" (with a standard normal density overlaid). Default: "qq"} + +\item{comp_type}{String. Type of composition to keep when \code{dat} contains +more than one. Options: "age", "length", or NULL (keep all). Default: NULL} + +\item{fleet}{Character or numeric vector. Fleet(s) to keep, matching +either the fleet name (e.g., "Fishery_current") or the fleet name with its +partition (e.g., "Fishery_current (retained)"). NULL keeps all fleets. Default: NULL} + +\item{ncol}{Number. Number of facet columns. NULL lets \code{ggplot2} choose. Default: NULL} + +\item{direction}{String. Order of the panels: "v" fills panels down each column +and then across, so consecutive years are stacked vertically and cohorts +are easier to follow from panel to panel; "h" fills panels across each row. + +Default: "v"} +} +\value{ +A \code{ggplot2} object. +} +\description{ +Quantile-quantile plot or histogram of Pearson residuals for each fleet, +to check for heavy tails, skewness, and outliers. +} +\details{ +The standard deviation of the normalized residuals (SDNR) for +each fleet is included in the panel label. Values well above one suggest +that the compositions are given more weight (larger sample sizes) than the +fit supports. Pearson residuals for compositions are not expected to be +exactly normally distributed, especially for bins with small expected +proportions or small sample sizes, so treat departures in the tails with +some caution. +} +\examples{ +plot_composition_residual_distribution(bigskate_lencomp) +plot_composition_residual_distribution(bigskate_lencomp, type = "histogram") +} +\seealso{ +\code{\link[=plot_composition_residuals]{plot_composition_residuals()}}, \code{\link[=plot_composition_residual_pattern]{plot_composition_residual_pattern()}}, +\code{\link[=prepare_composition_data]{prepare_composition_data()}} +} diff --git a/man/plot_composition_residual_pattern.Rd b/man/plot_composition_residual_pattern.Rd new file mode 100644 index 00000000..5299a970 --- /dev/null +++ b/man/plot_composition_residual_pattern.Rd @@ -0,0 +1,66 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot_composition_diagnostics.R +\name{plot_composition_residual_pattern} +\alias{plot_composition_residual_pattern} +\title{Plot patterns in composition residuals by bin or year} +\usage{ +plot_composition_residual_pattern( + dat, + by = c("bin", "year"), + comp_type = NULL, + fleet = NULL, + ncol = NULL, + direction = "v" +) +} +\arguments{ +\item{dat}{A tibble returned from \code{\link[=convert_output]{convert_output()}} that contains +composition data (see \link{bigskate_lencomp}), or composition data in any other +format accepted by \code{\link[=prepare_composition_data]{prepare_composition_data()}}.} + +\item{by}{String. Summarize residuals by "bin" (age or length) or by +"year". Default: "bin"} + +\item{comp_type}{String. Type of composition to keep when \code{dat} contains +more than one. Options: "age", "length", or NULL (keep all). Default: NULL} + +\item{fleet}{Character or numeric vector. Fleet(s) to keep, matching +either the fleet name (e.g., "Fishery_current") or the fleet name with its +partition (e.g., "Fishery_current (retained)"). NULL keeps all fleets. Default: NULL} + +\item{ncol}{Number. Number of facet columns. NULL lets \code{ggplot2} choose. Default: NULL} + +\item{direction}{String. Order of the panels: "v" fills panels down each column +and then across, so consecutive years are stacked vertically and cohorts +are easier to follow from panel to panel; "h" fills panels across each row. + +Default: "v"} +} +\value{ +A \code{ggplot2} object. +} +\description{ +Plot the mean Pearson residual for each bin (across years) or each year +(across bins), with a runs test for non-random patterns in the signs of +the residuals. These plots summarize the stripes that can appear in +residual bubble plots, for example, consistently negative residuals at +older ages caused by mis-specified selectivity. +} +\details{ +Vertical lines are plus and minus two standard errors of the mean +residual. The runs test (Wald-Wolfowitz) is applied to the signs of the +residuals ordered by year (when \code{by = "bin"}) or by bin (when +\code{by = "year"}). Filled points have fewer runs than expected by chance +(one-sided p < 0.05), which indicates a non-random pattern of residuals. +When all residuals have the same sign, the p-value is the probability of +that happening by chance, \eqn{2 \times 0.5^n}. +Residuals are summarized separately for each fleet and sex. +} +\examples{ +plot_composition_residual_pattern(bigskate_lencomp) +plot_composition_residual_pattern(bigskate_lencomp, by = "year") +} +\seealso{ +\code{\link[=plot_composition_residuals]{plot_composition_residuals()}}, \code{\link[=plot_composition_residual_distribution]{plot_composition_residual_distribution()}}, +\code{\link[=prepare_composition_data]{prepare_composition_data()}} +} diff --git a/man/plot_composition_residuals.Rd b/man/plot_composition_residuals.Rd new file mode 100644 index 00000000..e1810bce --- /dev/null +++ b/man/plot_composition_residuals.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot_composition.R +\name{plot_composition_residuals} +\alias{plot_composition_residuals} +\title{Plot composition Pearson residuals} +\usage{ +plot_composition_residuals(dat, ...) +} +\arguments{ +\item{dat}{A tibble returned from \code{\link[=convert_output]{convert_output()}} that contains +composition data (see \link{bigskate_lencomp}), or composition data in any other +format accepted by \code{\link[=prepare_composition_data]{prepare_composition_data()}}.} + +\item{...}{Additional arguments passed to \code{\link[=plot_composition_bubble]{plot_composition_bubble()}}.} +} +\value{ +A \code{ggplot2} object. +} +\description{ +A convenience wrapper around \code{\link[=plot_composition_bubble]{plot_composition_bubble()}} with +\code{value = "pearson"}. Residuals are taken from \code{dat} when supplied or +calculated by \code{\link[=prepare_composition_data]{prepare_composition_data()}} assuming a multinomial distribution. +} +\section{Equivalent plots in other packages}{ + +r4ss: \code{r4ss::SSplotComps()} subplot 2 for residuals by +fleet and subplot 24 (residuals compared across fleets). +} + +\examples{ +plot_composition_residuals(bigskate_lencomp) + +# Compare residuals across fleets for females +plot_composition_residuals( + dplyr::filter(bigskate_lencomp, sex == "female"), + ncol = 1 +) +} +\seealso{ +\code{\link[=plot_composition_bubble]{plot_composition_bubble()}}, \code{\link[=prepare_composition_data]{prepare_composition_data()}} +} diff --git a/man/plot_composition_samples.Rd b/man/plot_composition_samples.Rd new file mode 100644 index 00000000..e6ad109f --- /dev/null +++ b/man/plot_composition_samples.Rd @@ -0,0 +1,92 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot_composition_diagnostics.R +\name{plot_composition_samples} +\alias{plot_composition_samples} +\title{Plot input versus effective sample size of compositions} +\usage{ +plot_composition_samples( + dat, + comp_type = NULL, + fleet = NULL, + show_years = FALSE, + ncol = NULL, + direction = "v" +) +} +\arguments{ +\item{dat}{A tibble returned from \code{\link[=convert_output]{convert_output()}} that contains +composition data (see \link{bigskate_lencomp}), or composition data in any other +format accepted by \code{\link[=prepare_composition_data]{prepare_composition_data()}}. For Stock Synthesis +output converted from a Report.sso file without compositions by bin, the +adjusted input (\code{nsamp_adj}) and effective (\code{effn}) sample sizes in the +\code{FIT_LEN_COMPS} and \code{FIT_AGE_COMPS} tables are used instead (as in +\code{stockplotr::example_data}).} + +\item{comp_type}{String. Type of composition to keep when \code{dat} contains +more than one. Options: "age", "length", or NULL (keep all). Default: NULL} + +\item{fleet}{Character or numeric vector. Fleet(s) to keep, matching +either the fleet name (e.g., "Fishery_current") or the fleet name with its +partition (e.g., "Fishery_current (retained)"). NULL keeps all fleets. Default: NULL} + +\item{show_years}{Logical. TRUE/FALSE; label each point with its year. Default: FALSE} + +\item{ncol}{Number. Number of facet columns. NULL lets \code{ggplot2} choose. Default: NULL} + +\item{direction}{String. Order of the panels: "v" fills panels down each column +and then across, so consecutive years are stacked vertically and cohorts +are easier to follow from panel to panel; "h" fills panels across each row. + +Default: "v"} +} +\value{ +A \code{ggplot2} object. +} +\description{ +Compare the input (or adjusted input) sample size of each composition with +its effective sample size, the sample size implied by how well the model +fits the composition. This is the diagnostic behind the McAllister and +Ianelli (1997) data weighting method. +} +\details{ +For each composition (year and fleet), the effective sample size +is +\deqn{N_{eff} = \sum_b e_b (1 - e_b) / \sum_b (o_b - e_b)^2} +where \eqn{o_b} and \eqn{e_b} are the observed and expected proportions in +bin \eqn{b} (McAllister and Ianelli 1997), the same calculation used by +Stock Synthesis. + +Each panel shows a 1:1 line, a loess smoother (dashed red, when there are +more than six distinct input sample sizes), the arithmetic mean of the +input sample sizes (vertical dashed line), and the harmonic mean of the +effective sample sizes (horizontal dashed line). The ratio of the harmonic +mean effective sample size to the mean input sample size, the +McAllister-Ianelli weight, is included in each panel label. Ratios below +one suggest that input sample sizes are too large relative to the fit. As +in Stock Synthesis, the weight is calculated across all partitions of a +fleet; multiplying it by the current variance adjustment gives the +\code{Recommend_var_adj} reported by Stock Synthesis. + +Effective sample sizes from this formula are not appropriate for +compositions fit with a Dirichlet-multinomial likelihood. +} +\section{Equivalent plots in other packages}{ + +r4ss: \code{r4ss::SSplotComps()} subplot 7 ("N-EffN +comparison"). +} + +\examples{ +plot_composition_samples(bigskate_lencomp) + +# Stock Synthesis output converted with convert_output() +plot_composition_samples(stockplotr::example_data, comp_type = "length") +} +\references{ +McAllister, M.K., and Ianelli, J.N. (1997). Bayesian stock +assessment using catch-age data and the sampling-importance resampling +algorithm. Canadian Journal of Fisheries and Aquatic Sciences 54: 284-300. +} +\seealso{ +\code{\link[=prepare_composition_data]{prepare_composition_data()}}, \code{\link[=plot_composition_francis]{plot_composition_francis()}} +} diff --git a/man/plot_composition_sexratio.Rd b/man/plot_composition_sexratio.Rd new file mode 100644 index 00000000..1459ad50 --- /dev/null +++ b/man/plot_composition_sexratio.Rd @@ -0,0 +1,78 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot_composition.R +\name{plot_composition_sexratio} +\alias{plot_composition_sexratio} +\title{Plot sex ratios implied by composition data} +\usage{ +plot_composition_sexratio( + dat, + aggregate = FALSE, + interval = 0.75, + comp_type = NULL, + fleet = NULL, + ncol = NULL, + direction = "v" +) +} +\arguments{ +\item{dat}{A tibble returned from \code{\link[=convert_output]{convert_output()}} that contains +composition data (see \link{bigskate_lencomp}), or composition data in any other +format accepted by \code{\link[=prepare_composition_data]{prepare_composition_data()}}.} + +\item{aggregate}{Logical. TRUE/FALSE; aggregate compositions across years +within each fleet. Years are weighted by their sample size when sample +sizes are available. Default: FALSE} + +\item{interval}{Number. Coverage of the interval around the observed sex +ratio, calculated as a Jeffreys interval using the sample size in each bin +(the sample size multiplied by the proportion of the composition in the +bin). Set to NULL to omit intervals. Intervals are only shown when sample +sizes are available. Default: 0.75} + +\item{comp_type}{String. Type of composition to keep when \code{dat} contains +more than one. Options: "age", "length", or NULL (keep all). Default: NULL} + +\item{fleet}{Character or numeric vector. Fleet(s) to keep, matching +either the fleet name (e.g., "Fishery_current") or the fleet name with its +partition (e.g., "Fishery_current (retained)"). NULL keeps all fleets. Default: NULL} + +\item{ncol}{Number. Number of facet columns. NULL lets \code{ggplot2} choose. Default: NULL} + +\item{direction}{String. Order of the panels: "v" fills panels down each column +and then across, so consecutive years are stacked vertically and cohorts +are easier to follow from panel to panel; "h" fills panels across each row. + +Default: "v"} +} +\value{ +A \code{ggplot2} object. +} +\description{ +Plot the proportion female, female / (female + male), by bin for the +observed and expected compositions. Trends in the lack of fit to sex ratios +can be easier to see here than in residuals, especially for bins with +small proportions. +} +\details{ +Requires sex-specific compositions with both "female" and +"male" values in the \code{sex} column of \code{dat}. Points show observed ratios +and lines show expected ratios. As in r4ss, the smallest observed +proportion in each composition (such as the small constant Stock Synthesis +adds to every bin) is subtracted from the observed and expected values +first, so bins with no fish observed are omitted. When \code{aggregate = TRUE}, female and male +compositions are summed across years (weighted by sample size when +available) before calculating ratios. +} +\section{Equivalent plots in other packages}{ + +r4ss: \code{r4ss::SSplotSexRatio()} with +\code{sexratio.option = 2} (female:total). +} + +\examples{ +plot_composition_sexratio(bigskate_lencomp, fleet = "WCGBTS") +plot_composition_sexratio(bigskate_lencomp, aggregate = TRUE) +} +\seealso{ +\code{\link[=prepare_composition_data]{prepare_composition_data()}}, \code{\link[=plot_composition_distribution]{plot_composition_distribution()}} +} diff --git a/man/prepare_composition_data.Rd b/man/prepare_composition_data.Rd new file mode 100644 index 00000000..8b234829 --- /dev/null +++ b/man/prepare_composition_data.Rd @@ -0,0 +1,116 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils_composition.R +\name{prepare_composition_data} +\alias{prepare_composition_data} +\title{Prepare composition data for plotting} +\usage{ +prepare_composition_data( + dat, + comp_type = NULL, + fleet = NULL, + normalize = TRUE, + conditional = FALSE +) +} +\arguments{ +\item{dat}{A tibble returned from \code{\link[=convert_output]{convert_output()}} (see +\link{bigskate_lencomp}). Compositions are the rows whose \code{label} contains +"composition", for example: +\describe{ +\item{Stock Synthesis (from an \code{r4ss::SS_output()} list)}{ +"length_composition_observed", "length_composition_predicted", +"length_composition_pearson_residual", and +"length_composition_sample_size" (and the "age_" equivalents), with +conditional age-at-length data labeled "conditional_age_composition_*".} +\item{FIMS}{"age_composition_proportion" and "age_composition_expected" +(and the "length_" equivalents).} +\item{BAM and Rceattle}{"age_composition_observed" and +"age_composition_predicted", or "composition_observed" and +"composition_predicted".} +} +Bins are taken from the \code{age} or \code{length_bins} column, and partitions from +the \code{part} column. Other formats are also recognized: +\describe{ +\item{Tidy composition data}{Columns \code{year}, \code{bin}, and \code{observed}, with +optional columns \code{fleet}, \code{sex}, \code{part} (partition: 0 = whole catch, +1 = discard, 2 = retained), \code{expected}, \code{n} (input or adjusted sample +size), \code{pearson}, \code{comp_type}, and \code{length_bin} (for conditional +age-at-length data).} +\item{FIMS estimates}{Output from \code{FIMS::get_estimates()} that contains +\code{observed} and \code{expected} columns. Rows whose \code{label} contains "comp" are +kept and the bin is taken from the \code{age} or \code{length} column.} +\item{r4ss output}{A list returned by \code{r4ss::SS_output()} (length and +age compositions are combined and fleet names are added) or its +\code{lendbase} or \code{agedbase} elements (columns \code{Yr}, \code{Fleet}, \code{Sex}, \code{Bin}, +\code{Obs}, \code{Exp}, \code{Pearson}, \code{Nsamp_adj}, ...). Conditional +age-at-length rows are dropped from \code{agedbase} so that only marginal age +compositions remain, unless \code{conditional = TRUE}.} +} +For conditional age-at-length data (\code{conditional = TRUE}), see +\link{bigskate_caal}.} + +\item{comp_type}{String. Type of composition to keep when \code{dat} contains +more than one. Options: "age", "length", or NULL (keep all). Default: NULL} + +\item{fleet}{Character or numeric vector. Fleet(s) to keep, matching +either the fleet name (e.g., "Fishery_current") or the fleet name with its +partition (e.g., "Fishery_current (retained)"). NULL keeps all fleets. Default: NULL} + +\item{normalize}{Logical. TRUE/FALSE; rescale observed and expected values +so that they sum to one within each year, fleet, and composition type +(and length bin for conditional data). Sex-specific compositions are +normalized jointly across sexes, unless each sex already sums to one on +its own (single-sex compositions). Use TRUE when \code{dat} contains counts +rather than proportions. Default: TRUE} + +\item{conditional}{Logical. TRUE/FALSE; keep conditional age-at-length +data instead of marginal compositions. Conditional compositions are +normalized within each length bin. Used by \code{\link[=plot_composition_andre]{plot_composition_andre()}} and +\code{\link[=plot_composition_bubble]{plot_composition_bubble()}}. Default: FALSE} +} +\value{ +A tibble with one row per year, fleet, sex, and bin and columns +\code{comp_type}, \code{fleet}, \code{year}, \code{sex}, \code{bin}, \code{observed}, \code{expected}, \code{n}, +and \code{pearson} (plus \code{length_bin} when \code{conditional = TRUE}). Columns that +cannot be determined from \code{dat} are \code{NA}. +} +\description{ +Reshape age or length composition data (observed and, optionally, +expected proportions) from \code{\link[=convert_output]{convert_output()}} into the tidy format used by +the composition plotting functions (\code{\link[=plot_composition_distribution]{plot_composition_distribution()}}, +\code{\link[=plot_composition_bubble]{plot_composition_bubble()}}, \code{\link[=plot_composition_residuals]{plot_composition_residuals()}}, +\code{\link[=plot_composition_sexratio]{plot_composition_sexratio()}}, \code{\link[=plot_composition_francis]{plot_composition_francis()}}, +\code{\link[=plot_composition_samples]{plot_composition_samples()}}, \code{\link[=plot_composition_residual_distribution]{plot_composition_residual_distribution()}}, +\code{\link[=plot_composition_residual_pattern]{plot_composition_residual_pattern()}}, and \code{\link[=plot_composition_andre]{plot_composition_andre()}}). +Each of these functions calls \code{prepare_composition_data()} itself, so it +only needs to be called directly to inspect or modify the data. +} +\details{ +When Pearson residuals are not supplied, they are calculated +assuming a multinomial distribution as +\deqn{r = (o - e) / \sqrt{e (1 - e) / N}} +where \eqn{o} and \eqn{e} are the observed and expected proportions and +\eqn{N} is the sample size in the \code{n} column. If \code{n} is not available, +residuals are left as \code{NA}. +} +\examples{ +comps <- prepare_composition_data(bigskate_lencomp, fleet = "WCGBTS") +head(comps) + +# Conditional age-at-length data +prepare_composition_data(bigskate_caal, conditional = TRUE) + +# Data in the stockplotr long format are reshaped automatically +long <- data.frame( + label = rep(c("age_composition_proportion", "age_composition_expected"), each = 3), + year = 2020, + fleet = "fleet1", + age = rep(1:3, 2), + estimate = c(0.2, 0.5, 0.3, 0.25, 0.45, 0.3) +) +prepare_composition_data(long) +} +\seealso{ +\code{\link[=plot_composition_distribution]{plot_composition_distribution()}}, \code{\link[=plot_composition_bubble]{plot_composition_bubble()}}, \code{\link[=plot_composition_residuals]{plot_composition_residuals()}}, +\code{\link[=plot_composition_sexratio]{plot_composition_sexratio()}}, \code{\link[=plot_composition_francis]{plot_composition_francis()}} +} diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index fd62ae0e..51f9f217 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -27,6 +27,8 @@ navbar: href: articles/how-caps-alttext-are-made.html - text: Standardizing Assessment Model Output href: articles/convert_output_details.html + - text: Composition data diagnostics + href: articles/composition_diagnostics.html - text: Automating data extraction for SIS href: articles/extract-sis-data.html diff --git a/tests/testthat/test-convert_output.R b/tests/testthat/test-convert_output.R index 51d111c5..1fc956bc 100644 --- a/tests/testthat/test-convert_output.R +++ b/tests/testthat/test-convert_output.R @@ -96,6 +96,26 @@ test_that("r4ss::ss_output object is compatible.", { expect_equal(dim(result)[2], 35) }) +test_that("r4ss composition data are converted by bin", { + simple_r4ss <- readRDS(fs::path("fixtures", "r4ss_output", "simple_small", "simple_r4ss.rds")) + result <- suppressMessages(convert_output(simple_r4ss)) + + comps <- dplyr::filter(result, module_name %in% c("lendbase", "agedbase")) + expect_setequal( + unique(comps$label), + c( + paste0("length_composition_", c("observed", "predicted", "pearson_residual", "sample_size")), + paste0("age_composition_", c("observed", "predicted", "pearson_residual", "sample_size")) + ) + ) + # one row per composition bin, with values from r4ss + len_obs <- dplyr::filter(comps, label == "length_composition_observed") + expect_equal(nrow(len_obs), nrow(simple_r4ss$lendbase)) + expect_equal(sort(len_obs$estimate), sort(simple_r4ss$lendbase$Obs)) + expect_true(all(len_obs$fleet %in% simple_r4ss$FleetNames)) + expect_setequal(unique(len_obs$sex), c("female", "male")) +}) + test_that("URL input is compatible", { expect_no_error(result <- convert_output( file = "https://raw.githubusercontent.com/pfmc-assessments/petrale/main/models/2023.a050.003_FIMS_case-study_wtatage/Report.sso" diff --git a/tests/testthat/test-plot_composition.R b/tests/testthat/test-plot_composition.R new file mode 100644 index 00000000..ae7be7c4 --- /dev/null +++ b/tests/testthat/test-plot_composition.R @@ -0,0 +1,244 @@ +r4ss_output <- readRDS( + test_path("fixtures", "r4ss_output", "simple_small", "simple_r4ss.rds") +) + +has_layer <- function(p, geom_class) { + any(vapply(p$layers, function(l) inherits(l$geom, geom_class), logical(1))) +} + +# prepare_composition_data ---------------------------------------------------------- +# Big Skate values for one label from convert_output() output +bigskate_values <- function(dat, value) { + dat$estimate[grepl(paste0("_composition_", value, "$"), dat$label)] +} + +test_that("prepare_composition_data reads convert_output() output", { + comps <- prepare_composition_data(stockplotr::bigskate_lencomp) + expect_s3_class(comps, "stockplotr_comp") + expect_named( + comps, + c("comp_type", "fleet", "year", "sex", "bin", "observed", "expected", "n", "pearson") + ) + # proportions sum to one across sexes within year, fleet, and partition + sums <- comps |> + dplyr::summarise(total = sum(observed), .by = c(fleet, year)) + expect_true(all(abs(sums$total - 1) < 1e-6)) + # Stock Synthesis Pearson residuals and sample sizes are kept + expect_equal( + sort(comps$pearson), + sort(bigskate_values(stockplotr::bigskate_lencomp, "pearson_residual")) + ) + expect_setequal( + unique(comps$n), + unique(bigskate_values(stockplotr::bigskate_lencomp, "sample_size")) + ) +}) + +test_that("prepare_composition_data calculates multinomial Pearson residuals", { + comps <- prepare_composition_data( + dplyr::filter(stockplotr::bigskate_lencomp, label != "length_composition_pearson_residual") + ) + i <- 10 + expect_equal( + comps$pearson[i], + (comps$observed[i] - comps$expected[i]) / + sqrt(comps$expected[i] * (1 - comps$expected[i]) / comps$n[i]) + ) +}) + +test_that("prepare_composition_data filters by fleet and comp_type", { + comps <- prepare_composition_data(stockplotr::bigskate_lencomp, fleet = "WCGBTS") + expect_equal(unique(comps$fleet), "WCGBTS") + expect_error( + prepare_composition_data(stockplotr::bigskate_lencomp, fleet = "not a fleet"), + "No composition data" + ) + expect_error( + prepare_composition_data(stockplotr::bigskate_lencomp, comp_type = "age"), + "No composition data" + ) +}) + +test_that("partitions are added to fleet names as in r4ss", { + comps <- prepare_composition_data(stockplotr::bigskate_lencomp) + expect_setequal( + unique(comps$fleet), + c("Fishery_current (discard)", "Fishery_current (retained)", "WCGBTS", "Triennial") + ) + # a fleet can be selected with or without its partition + both <- prepare_composition_data(stockplotr::bigskate_lencomp, fleet = "Fishery_current") + expect_equal(length(unique(both$fleet)), 2) + retained <- prepare_composition_data(stockplotr::bigskate_lencomp, fleet = "Fishery_current (retained)") + expect_equal(unique(retained$fleet), "Fishery_current (retained)") +}) + +test_that("single-sex compositions are normalized separately", { + one_sex <- data.frame( + year = 2000, + sex = rep(c("female", "male"), each = 3), + bin = rep(1:3, 2), + observed = c(0.2, 0.3, 0.5, 0.1, 0.1, 0.8) + ) + comps <- prepare_composition_data(one_sex) + expect_equal(comps$observed, one_sex$observed) + # joint (two-sex) compositions are normalized together + joint <- one_sex + joint$observed <- joint$observed / 2 + expect_equal(prepare_composition_data(joint)$observed, joint$observed) +}) + +test_that("prepare_composition_data normalizes counts", { + counts <- data.frame(year = 2000, bin = 1:4, observed = c(10, 20, 30, 40)) + comps <- prepare_composition_data(counts) + expect_equal(comps$observed, c(0.1, 0.2, 0.3, 0.4)) + expect_equal(unique(comps$sex), "combined") + expect_true(all(is.na(comps$expected))) +}) + +test_that("prepare_composition_data reshapes stockplotr long format", { + long <- data.frame( + label = rep(c("age_composition_proportion", "age_composition_expected"), each = 3), + year = 2020, + fleet = "fleet1", + age = rep(1:3, 2), + estimate = c(0.2, 0.5, 0.3, 0.25, 0.45, 0.3) + ) + comps <- prepare_composition_data(long) + expect_equal(nrow(comps), 3) + expect_equal(comps$observed, c(0.2, 0.5, 0.3)) + expect_equal(comps$expected, c(0.25, 0.45, 0.3)) + expect_equal(unique(comps$comp_type), "age") +}) + +test_that("prepare_composition_data reads FIMS-style observed/expected columns", { + fims <- data.frame( + module_name = "Fleet", + label = rep(c("agecomp_expected", "lengthcomp_expected"), each = 4), + fleet = "survey1", + year = 1, + age = c(1:4, rep(NA, 4)), + length = c(rep(NA, 4), seq(10, 40, by = 10)), + observed = c(1, 2, 3, 4, 4, 3, 2, 1), + expected = c(1.5, 2, 3, 3.5, 4, 3, 2, 1) + ) + comps <- prepare_composition_data(fims) + expect_setequal(comps$comp_type, c("age", "length")) + len <- prepare_composition_data(fims, comp_type = "length") + expect_equal(len$bin, c(10, 20, 30, 40)) + expect_equal(sum(len$observed), 1) +}) + +test_that("prepare_composition_data reads r4ss output", { + comps <- prepare_composition_data(r4ss_output) + expect_setequal(comps$comp_type, c("age", "length")) + expect_true("FISHERY" %in% comps$fleet) + expect_setequal(unique(comps$sex), c("female", "male")) + # r4ss Pearson residuals are kept rather than recalculated + len <- prepare_composition_data(r4ss_output$lendbase) + expect_equal( + sort(len$pearson), + sort(as.numeric(r4ss_output$lendbase$Pearson)) + ) +}) + +# plot_composition_distribution ------------------------------------------------------------------ +test_that("plot_composition_distribution makes observation-only and fit plots", { + p_obs <- plot_composition_distribution(stockplotr::bigskate_lencomp, fleet = "WCGBTS", show_expected = FALSE) + expect_s3_class(p_obs, "ggplot") + expect_true(has_layer(p_obs, "GeomCol")) + expect_false(has_layer(p_obs, "GeomLine")) + + p_fit <- plot_composition_distribution(stockplotr::bigskate_lencomp, fleet = "Triennial") + expect_true(has_layer(p_fit, "GeomLine")) + # males are mirrored below zero + expect_true(any(p_fit$data$obs_plot < 0)) + # no sample size labels in the panels + expect_false(has_layer(p_fit, "GeomText")) +}) + +test_that("plot_composition_distribution aggregates across years", { + p <- plot_composition_distribution(stockplotr::bigskate_lencomp, aggregate = TRUE) + expect_s3_class(p, "ggplot") + expect_false("year" %in% names(p$data)) + sums <- p$data |> + dplyr::summarise(total = sum(observed), .by = fleet) + expect_true(all(abs(sums$total - 1) < 1e-8)) +}) + +test_that("plot_composition_distribution works with r4ss output", { + expect_no_error(plot_composition_distribution(r4ss_output, comp_type = "length", fleet = "FISHERY")) +}) + +# plot_composition_bubble / plot_composition_residuals -------------------------------------- +test_that("bubble plots show observations and residuals", { + p <- plot_composition_bubble(stockplotr::bigskate_lencomp) + expect_s3_class(p, "ggplot") + expect_true(has_layer(p, "GeomPoint")) + + r <- plot_composition_residuals(stockplotr::bigskate_lencomp) + expect_s3_class(r, "ggplot") + expect_true(all(c("Observed > expected", "Observed < expected") %in% levels(r$data$sign))) + expect_true(any(grepl("max =", r$data$panel))) +}) + +test_that("residual plot errors without expected values or sample sizes", { + obs_only <- dplyr::filter(stockplotr::bigskate_lencomp, label == "length_composition_observed") + expect_error(plot_composition_residuals(obs_only), "No Pearson residuals") +}) + +# plot_composition_sexratio --------------------------------------------------------- +test_that("plot_composition_sexratio calculates ratios and intervals", { + p <- plot_composition_sexratio(stockplotr::bigskate_lencomp, fleet = "WCGBTS") + expect_s3_class(p, "ggplot") + expect_true(has_layer(p, "GeomLinerange")) + ratios <- p$data + ok <- !is.na(ratios$ratio_obs) + expect_true(all(ratios$ratio_obs[ok] >= 0 & ratios$ratio_obs[ok] <= 1)) + expect_true(all(ratios$lower[ok] <= ratios$ratio_obs[ok] + 1e-12)) + expect_true(all(ratios$upper[ok] >= ratios$ratio_obs[ok] - 1e-12)) + + # bins without fish (only the constant Stock Synthesis adds) are omitted + # rather than shown as 50:50 + expect_false(any(ratios$ratio_obs[ok] == 0.5 & ratios$bin[ok] > 180)) + + expect_no_error(plot_composition_sexratio(stockplotr::bigskate_lencomp, aggregate = TRUE)) +}) + +test_that("plot_composition_sexratio requires two sexes", { + combined <- dplyr::filter(stockplotr::bigskate_lencomp, sex == "female") + expect_error(plot_composition_sexratio(combined), "both") +}) + +# plot_composition_francis -------------------------------------------------------------- +test_that("plot_composition_francis matches r4ss Francis weights", { + # Values from r4ss::SSMethod.TA1.8() for the simple_small model + means <- comp_mean_table( + prepare_composition_data(r4ss_output, comp_type = "length", fleet = "FISHERY") + ) + expect_equal(1 / stats::var(means$std_resid), 1.3108142, tolerance = 1e-6) + means <- comp_mean_table( + prepare_composition_data(r4ss_output, comp_type = "age", fleet = "SURVEY1") + ) + expect_equal(1 / stats::var(means$std_resid), 2.667424, tolerance = 1e-6) + # Big Skate: partitions of a fleet are combined, as in r4ss + means <- comp_mean_table( + prepare_composition_data(stockplotr::bigskate_lencomp, fleet = "Fishery_current") + ) + expect_equal(1 / stats::var(means$std_resid), 1.0019480, tolerance = 1e-6) + expect_equal(unique(means$fleet), "Fishery_current (discard & retained)") + means <- comp_mean_table( + prepare_composition_data(stockplotr::bigskate_lencomp, fleet = "WCGBTS") + ) + expect_equal(1 / stats::var(means$std_resid), 1.0000112, tolerance = 1e-6) +}) + +test_that("plot_composition_francis plots observed and expected means", { + p <- plot_composition_francis(stockplotr::bigskate_lencomp) + expect_s3_class(p, "ggplot") + expect_true(has_layer(p, "GeomLine")) + expect_true(has_layer(p, "GeomErrorbar")) + expect_true(all(grepl("TA1.8 weight", p$data$fleet))) + + no_francis <- plot_composition_francis(stockplotr::bigskate_lencomp, show_francis = FALSE) + expect_false(has_layer(no_francis, "GeomErrorbar")) +}) diff --git a/tests/testthat/test-plot_composition_diagnostics.R b/tests/testthat/test-plot_composition_diagnostics.R new file mode 100644 index 00000000..6c60afa9 --- /dev/null +++ b/tests/testthat/test-plot_composition_diagnostics.R @@ -0,0 +1,198 @@ +r4ss_output <- readRDS( + test_path("fixtures", "r4ss_output", "simple_small", "simple_r4ss.rds") +) + +has_layer <- function(p, geom_class) { + any(vapply(p$layers, function(l) inherits(l$geom, geom_class), logical(1))) +} + +# plot_composition_samples ------------------------------------------------------- +test_that("effective sample sizes match Stock Synthesis", { + comps <- prepare_composition_data(r4ss_output, comp_type = "length") + effn <- comp_effn_table(comps) + ss3 <- r4ss_output$lendbase |> + dplyr::distinct(Yr, Fleet, effN) |> + dplyr::mutate(fleet = r4ss_output$FleetNames[Fleet]) + joined <- dplyr::inner_join(effn, ss3, by = c("year" = "Yr", "fleet")) + expect_gt(nrow(joined), 0) + expect_equal(joined$effn, joined$effN, tolerance = 1e-3) +}) + +test_that("plot_composition_samples works with composition data", { + p <- plot_composition_samples(stockplotr::bigskate_lencomp) + expect_s3_class(p, "ggplot") + expect_true(has_layer(p, "GeomAbline")) + expect_true(all(grepl("McAllister-Ianelli weight", p$data$panel))) + # Matches Stock Synthesis: Recommend_var_adj = Curr_Var_Adj * weight, with + # partitions of a fleet combined (Length_Comp_Fit_Summary for Big Skate) + weights <- dplyr::distinct(p$data, base_fleet, mi_weight) + ss3 <- data.frame( + base_fleet = c("Fishery_current", "WCGBTS", "Triennial"), + curr_var_adj = c(0.239885, 0.067420, 1), + recommend_var_adj = c(0.107271, 0.637793, 0.870899) + ) + joined <- dplyr::inner_join(weights, ss3, by = "base_fleet") + expect_equal(nrow(joined), 3) + # (tolerance allows for the precision of values printed by Stock Synthesis) + expect_equal(joined$mi_weight * joined$curr_var_adj, joined$recommend_var_adj, tolerance = 1e-3) + expect_no_error(plot_composition_samples(r4ss_output, comp_type = "age", show_years = TRUE)) +}) + +test_that("plot_composition_samples works with converted SS3 output", { + p <- plot_composition_samples(stockplotr::example_data, comp_type = "length") + expect_s3_class(p, "ggplot") + expect_true(all(p$data$comp_type == "length")) + # retained and discard compositions are separate panels + expect_true(any(grepl("retained", p$data$fleet))) + expect_true(any(grepl("discard", p$data$fleet))) + # conditional age-at-length comps are dropped from age comps + expect_message( + plot_composition_samples(stockplotr::example_data, comp_type = "age"), + "conditional age-at-length" + ) +}) + +test_that("plot_composition_samples errors without expected values", { + obs_only <- dplyr::filter(stockplotr::bigskate_lencomp, label != "length_composition_predicted") + expect_error(plot_composition_samples(obs_only), "could not be calculated") +}) + +# residual distribution and patterns ----------------------------------------- +test_that("plot_composition_residual_distribution makes qq plots and histograms", { + qq <- plot_composition_residual_distribution(stockplotr::bigskate_lencomp) + expect_s3_class(qq, "ggplot") + expect_true(has_layer(qq, "GeomPoint")) + expect_true(all(grepl("SDNR", qq$data$panel))) + + hist <- plot_composition_residual_distribution(stockplotr::bigskate_lencomp, type = "histogram") + expect_true(has_layer(hist, "GeomBar")) +}) + +test_that("plot_composition_residual_pattern summarizes by bin and year", { + p_bin <- plot_composition_residual_pattern(stockplotr::bigskate_lencomp) + expect_s3_class(p_bin, "ggplot") + expect_true("bin" %in% names(p_bin$data)) + # mean residual by bin matches a direct calculation + direct <- stockplotr::bigskate_lencomp |> + dplyr::filter( + label == "length_composition_pearson_residual", + fleet == "WCGBTS", sex == "female", length_bins == 50 + ) + check <- dplyr::filter(p_bin$data, fleet == "WCGBTS", sex == "female", bin == 50) + expect_equal(check$mean_resid, mean(direct$estimate)) + + p_year <- plot_composition_residual_pattern(stockplotr::bigskate_lencomp, by = "year") + expect_true("year" %in% names(p_year$data)) +}) + +test_that("residual plots error without residuals", { + obs_only <- dplyr::filter(stockplotr::bigskate_lencomp, label == "length_composition_observed") + expect_error(plot_composition_residual_distribution(obs_only), "No Pearson residuals") + expect_error(plot_composition_residual_pattern(obs_only), "No Pearson residuals") +}) + +test_that("runs_test detects patterns", { + expect_lt(runs_test(c(rep(1, 10), rep(-1, 10))), 0.05) + expect_gt(runs_test(rep(c(1, -1), 10)), 0.5) + expect_equal(runs_test(rep(-1, 8)), 2 * 0.5^8) + expect_true(is.na(runs_test(c(1, -1)))) +}) + +# conditional age-at-length -------------------------------------------------- +test_that("prepare_composition_data handles conditional age-at-length data", { + caal <- prepare_composition_data(stockplotr::bigskate_caal, conditional = TRUE) + expect_true("length_bin" %in% names(caal)) + # single-sex compositions stay normalized within each sex and length bin + sums <- caal |> + dplyr::summarise(total = sum(observed), .by = c(fleet, year, sex, length_bin)) + expect_true(all(abs(sums$total - 1) < 1e-6)) + observed <- stockplotr::bigskate_caal$estimate[ + stockplotr::bigskate_caal$label == "conditional_age_composition_observed" + ] + expect_equal(sort(caal$observed), sort(observed), tolerance = 1e-6) + # marginal plots refuse conditional data + expect_error(prepare_composition_data(stockplotr::bigskate_caal), "conditional = TRUE") + expect_error(plot_composition_distribution(stockplotr::bigskate_caal), "conditional = TRUE") + # and conditional plots need conditional data + expect_error( + prepare_composition_data(stockplotr::bigskate_lencomp, conditional = TRUE), + "No conditional" + ) + # tidy data need a length_bin column + expect_error( + prepare_composition_data( + data.frame(year = 2000, bin = 1:3, observed = 1:3), + conditional = TRUE + ), + "length_bin" + ) +}) + +test_that("prepare_composition_data reads conditional data from r4ss dbase", { + dbase <- r4ss_output$agedbase + # make half of the age comps conditional on a narrow length bin + dbase$Lbin_hi[seq_len(nrow(dbase) / 2)] <- dbase$Lbin_lo[seq_len(nrow(dbase) / 2)] + caal <- prepare_composition_data(dbase, conditional = TRUE) + expect_equal(nrow(caal), nrow(dbase) / 2) + marginal <- suppressMessages(prepare_composition_data(dbase)) + expect_equal(nrow(marginal), nrow(dbase) / 2) + # r4ss output without conditional data + expect_error(prepare_composition_data(r4ss_output, conditional = TRUE), "No conditional") +}) + +test_that("plot_composition_andre shows mean and SE of age at length", { + dat <- dplyr::filter(stockplotr::bigskate_caal, year %in% 2017:2018) + p <- plot_composition_andre(dat, fleet = "WCGBTS") + expect_s3_class(p, "ggplot") + expect_setequal(as.character(unique(p$data$statistic)), c("Mean age", "SE of mean age")) + expect_true(has_layer(p, "GeomRibbon")) + # observed mean ages increase with length + means <- dplyr::filter(p$data, statistic == "Mean age", year == 2018, sex == "female") + expect_gt(stats::cor(means$length_bin, means$observed), 0.8) + # one value per year, sex, and length bin, calculated as in r4ss + z <- stockplotr::bigskate_caal |> + dplyr::filter(fleet == "WCGBTS", year == 2018, sex == "female", length_bins == 60) |> + dplyr::select(label, estimate, age) |> + tidyr::pivot_wider(names_from = label, values_from = estimate) |> + dplyr::transmute( + bin = as.numeric(age), + observed = conditional_age_composition_observed, + expected = conditional_age_composition_predicted, + n = conditional_age_composition_sample_size + ) + check <- dplyr::filter(p$data, year == 2018, sex == "female", length_bin == 60) + obs_mean <- sum(z$bin * z$observed) / sum(z$observed) + expect_equal(check$observed[check$statistic == "Mean age"], obs_mean) + expect_equal( + check$expected[check$statistic == "SE of mean age"], + sqrt(sum(z$bin^2 * z$expected) / sum(z$expected) - + (sum(z$bin * z$expected) / sum(z$expected))^2) / sqrt(z$n[1]) + ) +}) + +test_that("conditional bubble plots use length bins on the x-axis", { + dat <- dplyr::filter(stockplotr::bigskate_caal, year %in% 2017:2018) + p <- plot_composition_residuals(dat, fleet = "WCGBTS", conditional = TRUE) + expect_s3_class(p, "ggplot") + expect_true(any(grepl("^female, 2017\n\\(max", levels(p$data$panel)))) + expect_no_error(plot_composition_bubble(dat, fleet = "WCGBTS", conditional = TRUE)) +}) + +test_that("r4ss sex codes are read correctly", { + # single-sex male compositions have the Stock Synthesis code Sex = 1 but + # r4ss sex = 2, and appear twice in the composition database + dbase <- data.frame( + Yr = 2000, Fleet = 1, Kind = "AGE", Part = 0, + Sexes = 2, Sex = c(1, 1, 2, 2), sex = 2, + Lbin_lo = 50, Lbin_hi = 50, Bin = c(1, 2, 1, 2), + Obs = c(0.4, 0.6, 0.4, 0.6), Exp = c(0.5, 0.5, 0.5, 0.5), + Pearson = c(-0.2, 0.2, -0.2, 0.2), Nsamp_adj = 10 + ) + comps <- prepare_composition_data(dbase, conditional = TRUE) + expect_equal(nrow(comps), 2) + expect_equal(unique(comps$sex), "male") + expect_equal(comps$observed, c(0.4, 0.6)) + # without the r4ss sex column, sex is derived from Sexes and Sex + comps <- prepare_composition_data(dplyr::select(dbase, -sex), conditional = TRUE) + expect_equal(unique(comps$sex), "male") +}) diff --git a/vignettes/composition_diagnostics.Rmd b/vignettes/composition_diagnostics.Rmd new file mode 100644 index 00000000..03afd102 --- /dev/null +++ b/vignettes/composition_diagnostics.Rmd @@ -0,0 +1,274 @@ +--- +title: "Composition data diagnostics" +output: + html_document: + toc: true +vignette: > + %\VignetteIndexEntry{Composition data diagnostics} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + fig.width = 9, + fig.height = 7, + out.width = "100%" +) +``` + +```{r setup} +library(stockplotr) +``` + +Age and length compositions are often the most informative, and the hardest +to fit, data in a stock assessment. {stockplotr} has a set of plots for +composition data based on the figures that +[r4ss](https://github.com/r4ss/r4ss) makes for Stock Synthesis models. Each +plot shows the data in a different way, and most have an equivalent in r4ss: + +| Plot | Function | What it shows | Equivalent in other packages | +|------|----------|---------------|-----------------| +| Distributions of observations | `plot_composition_distribution(show_expected = FALSE)` | The most intuitive way to look at compositions | r4ss: `SSplotComps()` subplot 1, `datonly = TRUE` | +| Bubble plot of observations | `plot_composition_bubble()` | Cohort strength over time | r4ss: `SSplotComps()` subplot 2, `datonly = TRUE` | +| Distributions of observed and expected values | `plot_composition_distribution()` | An intuitive way to look at fit | r4ss: `SSplotComps()` subplots 1 and 21 | +| Pearson residuals | `plot_composition_residuals()` | Blocks or stripes of misfit among years or bins | r4ss: `SSplotComps()` subplots 2 and 24 | +| Sex ratios | `plot_composition_sexratio()` | Trends in misfit for bins with small proportions, which have small residuals | r4ss: `SSplotSexRatio()` | +| Mean age or length (Francis) | `plot_composition_francis()` | Trends among years; Francis (2011) TA1.8 data weights | r4ss: `SSplotComps()` subplots 8 and 9, `SSMethod.TA1.8()` | +| N-EffN comparison | `plot_composition_samples()` | McAllister-Ianelli data weights | r4ss: `SSplotComps()` subplot 7 | +| Residual distribution | `plot_composition_residual_distribution()` | Heavy tails, skewness, and outliers; SDNR | | +| Residual patterns | `plot_composition_residual_pattern()` | Bias by bin or year, with a runs test | | +| Andre's plots | `plot_composition_andre()` | Mean age at length for conditional age-at-length data | r4ss: `SSplotComps()` subplot 12 | +| Conditional age-at-length residuals | `plot_composition_residuals(conditional = TRUE)` | Misfit by length and age within years | r4ss: `SSplotComps()` subplot 3 | + +Each function can plot fleets separately or summarize all fleets. Use `fleet` +to choose fleets, and `aggregate = TRUE` in `plot_composition_distribution()` and +`plot_composition_sexratio()` to combine all years. Panels are ordered down +each column by default, so consecutive years are stacked vertically and +cohorts are easier to follow; use `direction = "h"` to order them across rows. + +## Composition data + +Like the other {stockplotr} plotting functions, the composition plots take +the standard output from `convert_output()`. Each function calls +`prepare_composition_data()` to pull out the composition data, so the plots +work with any assessment model whose compositions `convert_output()` +supports. Compositions are the rows whose `label` contains "composition", +such as `length_composition_observed` and `length_composition_predicted`. + +This vignette uses data from the 2019 Big Skate stock assessment for the U.S. +West Coast, the same model used for the +[r4ss example plots](https://r4ss.github.io/r4ss/BigSkate/), so the figures +below can be compared directly with the r4ss versions. `bigskate_lencomp` +holds the length compositions and `bigskate_caal` the conditional +age-at-length data, both from `convert_output()` applied to the output of +`r4ss::SS_output()`: + +```{r} +bigskate_lencomp |> + dplyr::count(module_name, label) + +bigskate_lencomp |> + dplyr::select(label, estimate, year, fleet, sex, part, length_bins) |> + head() +``` + +The length compositions are sex-specific. `Fishery_current` has separate +retained (`part` = 2) and discarded (`part` = 1) compositions, which are +labeled "Fishery_current (retained)" and "Fishery_current (discard)", as in +r4ss. Use `fleet = "Fishery_current"` to select both. + +If Pearson residuals are not supplied, `prepare_composition_data()` calculates them +assuming a multinomial distribution, +$r = (o - e) / \sqrt{e (1 - e) / N}$, using the sample size. The Big +Skate data include the residuals and adjusted sample sizes reported by Stock +Synthesis. + +## Observations + +Distributions are the most intuitive way to look at compositions. For +sex-specific data, females are shown above zero and males below. + +```{r obs-dist, fig.height = 9} +plot_composition_distribution(bigskate_lencomp, fleet = "WCGBTS", show_expected = FALSE) +``` + +Bubble plots make it easier to see patterns across years, such as strong +cohorts moving through the population. + +```{r obs-bubble} +plot_composition_bubble(bigskate_lencomp) +``` + +## Fits to the data + +Adding the expected values to the distribution plot shows the fit to each +year. + +```{r fit-dist, fig.height = 4} +plot_composition_distribution(bigskate_lencomp, fleet = "Triennial") +``` + +Aggregating across years gives an overview of each fleet. Years are weighted +by their adjusted input sample sizes. + +```{r fit-agg, fig.height = 6} +plot_composition_distribution(bigskate_lencomp, aggregate = TRUE) +``` + +### Pearson residuals + +Pearson residuals are a more statistically rigorous way to look at fit. +Filled bubbles show where more fish were observed than expected, and open +bubbles where fewer were observed. The largest residual in each panel is +included in the label. + +```{r resid, fig.height = 8} +plot_composition_residuals(bigskate_lencomp) +``` + +### Sex ratios + +The fraction female in each bin, female / (female + male), can show trends +in misfit for bins with small proportions, since those bins have small +residuals. Vertical lines are Jeffreys intervals based on the sample size in +each bin. For Big Skate, the largest fish are almost all female. + +```{r sex-ratio, fig.height = 9} +plot_composition_sexratio(bigskate_lencomp, fleet = "WCGBTS") +``` + +```{r sex-ratio-agg, fig.height = 6} +plot_composition_sexratio(bigskate_lencomp, aggregate = TRUE) +``` + +### Mean length and Francis weights + +The time series of mean age or length was developed as a diagnostic for the +Francis (2011) data weighting method (TA1.8), but it is also a useful summary +of trends in the data and the fit. Thick intervals are based on the current +sample sizes, and thin, capped intervals show the result of further adjusting +sample sizes by the TA1.8 weight in each panel label. As in +`r4ss::SSMethod.TA1.8()`, partitions of a fleet are combined into one +composition per year. The weights near one show that this model was already +tuned with the Francis method. + +```{r francis, fig.height = 5} +set.seed(1) # the interval for the Francis weight is bootstrapped +plot_composition_francis(bigskate_lencomp) +``` + +### N-EffN comparison + +The effective sample size measures how well the model fits each composition +(McAllister and Ianelli 1997). If the input sample sizes suit the fit, the +points should scatter around the 1:1 line. The ratio of the harmonic mean +effective sample size to the mean input sample size, shown in each panel +label, is the McAllister-Ianelli weight. As in Stock Synthesis, it is +calculated across all partitions of a fleet, and multiplying it by the +current variance adjustment gives the `Recommend_var_adj` that Stock +Synthesis reports. + +```{r sample-size, fig.height = 6} +plot_composition_samples(bigskate_lencomp) +``` + +For Stock Synthesis models, the same plot can be made directly from +`convert_output()` output, using the adjusted input and effective sample +sizes that Stock Synthesis reports: + +```{r sample-size-ss3} +plot_composition_samples(stockplotr::example_data, comp_type = "length") +``` + +### Residual distribution and patterns + +A quantile-quantile plot shows whether residuals have heavier tails than +expected. The standard deviation of the normalized residuals (SDNR) is shown +in each panel label; values well above one suggest the data are given more +weight than the fit supports. + +```{r resid-qq, fig.height = 6} +plot_composition_residual_distribution(bigskate_lencomp) +``` + +Averaging residuals by bin or year turns stripes in the bubble plot into a +single line. Filled points fail a runs test, meaning the residual signs in +that bin are not randomly ordered across years. + +```{r resid-pattern, fig.height = 8} +plot_composition_residual_pattern(bigskate_lencomp) +``` + +## Conditional age-at-length data + +Conditional age-at-length data are age compositions for each length bin. In +`convert_output()` output they are labeled +`conditional_age_composition_*`, with the length bin in `length_bins`, as in +`bigskate_caal`. Use `conditional = TRUE` in `prepare_composition_data()` and +`plot_composition_bubble()`. The Big Skate conditional data are single-sex +compositions, which are normalized separately for each sex. + +`plot_composition_andre()` makes "Andre's plots" from r4ss: the observed and +expected mean age in each length bin (left), and the standard error of the +mean age (right), one row per year and sex. + +```{r caal-andre, fig.height = 10} +caal_recent <- dplyr::filter(bigskate_caal, year %in% 2016:2018) +plot_composition_andre(caal_recent, fleet = "WCGBTS") +``` + +Pearson residuals for conditional data are plotted by length bin and age for +each year: + +```{r caal-resid, fig.height = 6} +plot_composition_residuals(caal_recent, fleet = "WCGBTS", conditional = TRUE) +``` + +## Using model output + +With Stock Synthesis, convert the output of `r4ss::SS_output()` with +`convert_output()`. The composition data come from the `lendbase`, +`agedbase`, and `condbase` elements, including the Pearson residuals and +adjusted sample sizes reported by Stock Synthesis. The Francis weights match +`r4ss::SSMethod.TA1.8()`, and the effective sample sizes and +McAllister-Ianelli weights match those reported by Stock Synthesis. + +```{r ss3, eval = FALSE} +replist <- r4ss::SS_output("path/to/BigSkate_2019") +std_output <- convert_output(replist) +plot_composition_distribution(std_output, comp_type = "length", fleet = "WCGBTS") +plot_composition_francis(std_output, comp_type = "length") +plot_composition_andre(std_output, fleet = "WCGBTS") +``` + +The same functions work with output from the other models that +`convert_output()` supports. With FIMS, the estimates can also be passed +directly: + +```{r fims, eval = FALSE} +estimates <- FIMS::get_estimates(fit) +plot_composition_residuals(estimates, comp_type = "age") +plot_composition_francis(estimates, comp_type = "length") +``` + +FIMS reports years as indices (`year_i`); add a `year` column to plot +calendar years. FIMS estimates do not currently include composition sample +sizes. Add an `n` column, or supply Pearson residuals in a `pearson` column, +to get residuals and intervals. + +## References + +Francis, R.I.C.C. (2011). Data weighting in statistical fisheries stock +assessment models. Canadian Journal of Fisheries and Aquatic Sciences 68: +1124-1138. + +McAllister, M.K., and Ianelli, J.N. (1997). Bayesian stock assessment using +catch-age data and the sampling-importance resampling algorithm. Canadian +Journal of Fisheries and Aquatic Sciences 54: 284-300. + +Taylor, I.G., Gertseva, V., Stephens, A., and Bizzarro, J. (2019). Status of +Big Skate (*Beringraja binoculata*) off the U.S. Pacific Coast in 2019. +Pacific Fishery Management Council, Portland, OR. From 7712ccb822253773881303341ed47e4e4600d029 Mon Sep 17 00:00:00 2001 From: Sam Schiano <125507018+Schiano-NOAA@users.noreply.github.com> Date: Fri, 6 Nov 2026 09:52:29 -0500 Subject: [PATCH 2/3] move placement of added comp piece for conout ss3 and update r4ss gha to correct script --- .github/workflows/update-ss3-example-data.yml | 2 +- R/conout_ss3.R | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/update-ss3-example-data.yml b/.github/workflows/update-ss3-example-data.yml index 789c187f..600bd69f 100644 --- a/.github/workflows/update-ss3-example-data.yml +++ b/.github/workflows/update-ss3-example-data.yml @@ -27,7 +27,7 @@ jobs: extra-packages: any::pak, any::usethis, local::. - name: Update example data - run: Rscript data-raw/DATASET_SS3.R + run: Rscript data-raw/DATASET_SS3-r4ss.R - name: Commit and push updated example data if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository diff --git a/R/conout_ss3.R b/R/conout_ss3.R index a98e7198..5ae019a2 100644 --- a/R/conout_ss3.R +++ b/R/conout_ss3.R @@ -1257,17 +1257,7 @@ conout_ss3 <- function( ) } out_new <- Reduce(rbind, out_list) - out_new |> - dplyr::mutate( - fleet = if (any(unique(out_new$fleet) %in% fleet_names)) fleet else fleet_names[fleet], - era = dplyr::case_when( - !is.na(era) ~ era, - year < start_year ~ "init", - year >= start_year & year <= end_year ~ "time", - year > end_year ~ "fore", - TRUE ~ NA_character_ - ) - ) + # Composition data by bin from r4ss (lendbase, agedbase, condbase) if (!is.character(file)) { comp_rows <- ss3_composition_rows( @@ -1282,4 +1272,16 @@ conout_ss3 <- function( out_new <- rbind(out_new, comp_rows) } } + + out_new |> + dplyr::mutate( + fleet = if (any(unique(out_new$fleet) %in% fleet_names)) fleet else fleet_names[fleet], + era = dplyr::case_when( + !is.na(era) ~ era, + year < start_year ~ "init", + year >= start_year & year <= end_year ~ "time", + year > end_year ~ "fore", + TRUE ~ NA_character_ + ) + ) } From 9880ecaf3968e83cca904d81eecf3a1518ee3731 Mon Sep 17 00:00:00 2001 From: Sam Schiano <125507018+Schiano-NOAA@users.noreply.github.com> Date: Thu, 12 Nov 2026 16:48:10 -0500 Subject: [PATCH 3/3] update NAMESPACE --- NAMESPACE | 3 +-- man/table_total_catch.Rd | 7 ++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index b8a3424a..84f21a8f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -13,7 +13,7 @@ export(plot_aa) export(plot_abundance_at_age) export(plot_biomass) export(plot_biomass_at_age) -export(plot_catch_comp) +export(plot_catch_at_age) export(plot_composition_andre) export(plot_composition_bubble) export(plot_composition_distribution) @@ -23,7 +23,6 @@ export(plot_composition_residual_pattern) export(plot_composition_residuals) export(plot_composition_samples) export(plot_composition_sexratio) -export(plot_catch_at_age) export(plot_discard) export(plot_fishing_mortality) export(plot_index) diff --git a/man/table_total_catch.Rd b/man/table_total_catch.Rd index 25f6c5ce..5026642f 100644 --- a/man/table_total_catch.Rd +++ b/man/table_total_catch.Rd @@ -6,7 +6,7 @@ \usage{ table_total_catch( dat, - unit_label = "mt", + unit_label = NULL, era = NULL, interactive = TRUE, module = NULL, @@ -25,7 +25,7 @@ in \code{ref_line} is used to plot a reference line or calculate relative spawni \item{unit_label}{String. Abbreviated catch units -Default: "mt"} +Default: NULL} \item{era}{String. Era of data. @@ -81,7 +81,8 @@ There are options to return a \code{\link[gt:gt]{gt::gt()}} object or export an containing a gt-based table, caption, and LaTeX-based table. } \examples{ -table_total_catch(stockplotr::example_data, +table_total_catch( + stockplotr::example_data, module = "TIME_SERIES" )