Growth estimation with linkages and priors
Source:vignettes/growth-estimation.Rmd
growth-estimation.RmdOverview
This vignette shows how to estimate growth with Rceattle’s built-in
von Bertalanffy and Richards growth functions, and how to use the
formula-driven linkage_spec() API to add covariates and
priors to growth parameters.
The examples use the bundled whamGrowthData dataset,
which supplies conditional age-at-length data (caal_data)
plus the stock assessment inputs needed to estimate growth.
Load data and inspect the growth dataset
library(Rceattle)
data(whamGrowthData)
plot_data(whamGrowthData)
# The dataset includes CAAL data for the survey.
head(whamGrowthData$caal_data)
# Use logistic selectivity for all fleets in this example.
whamGrowthData$fleet_control$Selectivity <- "Logistic"
whamGrowthData$fleet_control$Selectivity_dimension <- "Length"Build a growth model with linkages
Growth linkages are built with build_growth() and
linkage_spec(). A linkage can act on any von Bertalanffy
parameter, K, Linf, or L1
(natural-scale names; the default link = "log" keeps the
underlying TMB parameter on the log scale), and can hold priors on the
linked coefficients.
# Create a simple environmental covariate for the example data.
set.seed(123)
yrs <- whamGrowthData$styr:whamGrowthData$endyr
whamGrowthData$env_data <- data.frame(
Year = yrs,
temp = rnorm(length(yrs), 0, 0.5)
)
growth_spec <- build_growth(
fun = "vonBertalanffy",
linkages = list(
Linf = linkage_spec(
formula = ~ temp, # 2 columns: (Intercept) and `temp`
by = ~ species, # one beta per (species, column)
priors = list(
# Intercept prior is on natural-scale Linf (log link back-transforms).
# Use lognormal() if you want a Normal on log(Linf) instead.
"(Intercept)" = normal(85, 5),
temp = normal(0, 0.1)) # Tight prior on the slope
)
)
)What this linkage does
-
formula = ~ tempfits an intercept and a temperature slope. -
by = ~ speciesgives each species its own intercept and slope (add+ sexto further split by sex). -
link = "log"(the default) keeps the underlying TMB parameter on the log scale, so the slope rows act multiplicatively on naturalLinf. -
priorsattaches a Normal prior to both the intercept and the temperature slope.
The (Intercept) prior is a prior on the base
Linf value (natural scale, because the default log link is
back-transformed inside the prior block). An intercept-bearing linkage
replaces the base parameter and holds its level.
Time-varying growth
The same grammar holds time variation, with a random-effect term on the right-hand side instead of (or alongside) a covariate:
# A random walk on the von Bertalanffy k. Use ar1(1 | Year) for a stationary
# autoregression, or (1 | Year) for independent annual deviations; under ar1()
# the `sigma` init is the MARGINAL standard deviation.
build_growth(
fun = "vonBertalanffy",
linkages = list(K = linkage_spec(~ rw(1 | Year)))
)This is the only route to time-varying growth. The
growth_re switch that earlier versions held in the
data_list was never implemented, nothing consumed it, the
deviation array was mapped off in every configuration, and the model
gave it no density, so it is removed as of 5.9.0 and setting it now
raises a deprecation message. Unlike the switch, a random linkage holds
a real density, so it is estimated, reported in the linkage table, and
redrawn by sim_mod(process = "growth").
Fit the model
vbgf_prior_fit <- fit_mod(
data_list = whamGrowthData,
growthFun = growth_spec,
estimateMode = 0,
msmMode = 0,
fit_control = fit_control(verbose = 0, phase = FALSE)
)
summary(vbgf_prior_fit)Inspect the linkage table and prior contributions
# The linkage table is stored on the fitted model.
vbgf_prior_fit$data_list$linkage_table
# The design matrix used by the linkages.
head(vbgf_prior_fit$data_list$linkage_X)
# Prior contribution appears in the joint NLL components.
vbgf_prior_fit$quantities$jnll_comp["Linkage-table priors", ]Compare with an unlinked baseline model and Richards model
baseline_fit <- Rceattle::fit_mod(
data_list = whamGrowthData,
inits = NULL, # Initial parameters at default
estimateMode = 0, # Estimate
growthFun = build_growth(fun = "vonBertalanffy"), # Von Bert
random_rec = FALSE, # No random recruitment
msmMode = 0, # Single species mode
initMode = "NonEquilibrium", # Unfished disequilibrium
fit_control = fit_control(phase = FALSE, verbose = 1))
richards_model <- Rceattle::fit_mod(
data_list = whamGrowthData,
inits = NULL, # Initial parameters from above
estimateMode = 0, # Estimate
growthFun = build_growth(fun = "Richards"), # Richards
random_rec = FALSE, # No random recruitment
msmMode = 0, # Single species mode
initMode = "NonEquilibrium", # Unfished disequilibrium
fit_control = fit_control(phase = FALSE, verbose = 1))
plot_biomass(
list(baseline_fit, richards_model, vbgf_prior_fit),
species = 1,
model_names = c("Baseline VBGF", "Richards", "VBGF with Linf linkage and priors"),
incl_proj = TRUE
)
baseline_fit$opt$AIC
vbgf_prior_fit$opt$AICMatching a Stock Synthesis growth specification
These options let an estimated-growth model reproduce how Stock Synthesis 3.30 turns a growth curve into an age-length key, weight-at-age and spawning output. Each defaults to Rceattle’s usual behaviour, so nothing changes until you set one.
| SS3 control input | Rceattle |
|---|---|
| Population length bins (data file) | build_growth(pop_lengths = <lower edges, cm>) |
CV_Growth_Pattern 0 (CV a function of
length-at-age) |
build_growth(sd_form = "CV"); sd_L1 /
sd_Linf are then CV_young / CV_old |
CV_Growth_Pattern 2 (SD a function of
length-at-age) |
sd_form = "SD" (default) |
Growth_Age_for_L2 = 999 |
sd_plus_group = "WHAM" (plus group at CV_old /
SD_old) |
Linf_decay -998 / -999 / d > 0 |
plus_group_length = "none" / "SS3.24" /
"decay" with plus_group_decay = d
|
| Maturity option 1 (length logistic), fecundity = weight | control columns L50_mat_len, slope_mat_len
(slope positive: SS3’s sign flipped) |
Growth_Age_for_L1 |
build_growth(growth_age_L1 =) |
For example, a Richards model with SS3 1-cm population bins from 0.5 to 142.5 cm, CVs, and no plus-group adjustment:
whamGrowthData$L50_mat_len <- 55.43 # cm
whamGrowthData$slope_mat_len <- 0.155 # per cm
growth_ss3 <- build_growth(
fun = "Richards",
growth_age_L1 = 1,
sd_form = "CV",
plus_group_length = "none",
sd_plus_group = "WHAM",
pop_lengths = seq(0.5, 142.5, by = 1)
)With maturity-at-length set, spawning biomass integrates maturity x
weight over the length distribution at spawning; the fitted object
reports the per-fish result as quantities$spawn_output.
Notes on priors through linkages
- A prior on
"(Intercept)"in a growth linkage constrains the base growth parameter itself. - A prior on a covariate column such as
tempconstrains that driver’s effect on the parameter. - The linkage API accepts any R-style formula and supports per-species or per-sex stratification.
- The length-at-age SD endpoints
sd_L1andsd_Linfare also linkage targets, so you can setinit/bounds/priorson them. Only intercept formulas (e.g.~ 1) are honored, the SDs do not vary by year, so slope terms have no effect. See the “Growth SD endpoints” section ofvignette("environmental-linkages-and-priors")for an example.