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.
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"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
)
)
)formula = ~ temp fits an intercept and a temperature
slope.by = ~ species gives each species its own intercept and
slope (add + sex to further split by sex).link = "log" (the default) keeps the underlying TMB
parameter on the log scale, so the slope rows act multiplicatively on
natural Linf.priors attaches 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.
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").
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$AICThese 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.
"(Intercept)" in a growth linkage constrains
the base growth parameter itself.temp constrains
that driver’s effect on the parameter.sd_L1 and
sd_Linf are also linkage targets, so you can set
init / bounds / priors on 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 of
vignette("environmental-linkages-and-priors") for an
example.