Estimates abundance, recruitment, and mortality from catch-at-age and survey
observations. Returns population trends, observation diagnostics, parameter
estimates, and uncertainty tables. Use N_settings, F_settings, and
M_settings to specify where unexplained biological variation is allowed.
See prepare_tam() for options and tinyAM-model for the model equations.
Usage
fit_tam(
data,
years = NULL,
ages = NULL,
N_settings = list(process = "iid", init = "exp", mu_form = NULL, sd_form = ~1),
rec_settings = list(process = "rw", init = "free", mu_form = NULL, sd = NULL, phi = NULL),
F_settings = list(process = "rw", mu_form = NULL, sd_form = ~1, age_breaks = NULL, cor_form = ~1,
mean_ages = NULL),
M_settings = list(process = "off", mu_form = NULL, mu_supplied = ~I(0.2), sd_form = ~1,
age_breaks = NULL, first_dev_year = NULL, mean_ages = NULL),
catch_settings = list(sd_form = ~1, sd_supplied = NULL),
index_settings = list(sd_form = ~1, sd_supplied = NULL, q_form = ~q_block, q_link = "log"),
proj_settings = NULL,
interval = 0.95,
add_osa_res = FALSE,
silent = FALSE,
start_par = NULL,
grad_tol = 0.01
)
# Optional projections:
# proj_settings = list(n_proj = 3, n_mean = 3, F_mult = 1, catch = NULL)Arguments
- data
A named list of tidy observation tables (e.g.,
catch,index,weight,maturity) and optionalpop_covariates. A tam_data object or plain list can be used. The weight table may distinguish complete stock and catch weights withtype; stock weights supply both purposes when catch weights are absent. See cod_obs for an example.- years
Consecutive historical model years (at least two). Inferred from observed data (non-projection) if
NULL.- ages
Consecutive model ages (at least two). Inferred from observed data (non-projection) if
NULL. If the ages in the data extend beyondmax(ages), the"obs"column is summed forcatchandindexdata within each year (and survey for indices). Older-age weight and maturity are retained for abundance-based aggregation insidenll_fun(), preserving biomass and mature biomass in the plus group.- N_settings
A list with elements:
process:"off"for deterministic cohort survival,"iid"for independent cohort residuals,"rw"for residuals that accumulate through time, or"ar1"for residuals correlated between years and ages. See tinyAM-model.init:"exp"(default),"free","latent","mono"or"capped", independently ofprocess. All use first-year recruitmentlog_r0as the starting anchor."exp"uses deterministic survivorship;"free"estimates fixed older-agelog_n0states;"latent"integrates them without an initial density."mono"constrains abundance to decline from r0;"capped"bounds each older age by r0 without imposing age ordering. See Details.mu_form: ordinary fixed formula for proportional cohort gains/losses, defaultNULL. Covariates come frompop_covariatesat the destination year and older age, after survivors are combined (including the plus group). Recruitment and initialization are unaffected. These effects do not identify immigration separately from mortality or observation changes. RW residuals require independent temporal mean contrasts; constant directions cancel and are rejected. See tinyAM-model.sd_form: ordinary formula for log SD of older-age survival residuals, default~ 1. Use~ factor(age)or a supplied age-sharing column indata$pop_covariates. Its design must be constant across years. Recruitment SD is controlled separately byrec_settings; initial abundance has no separate SD. Random-effect SD terms are not enabled.
- rec_settings
A list controlling recruitment separately from cohort survival:
process:"off"for recruitment exactly following its mean formula, or"rw"(default),"iid", or"ar1"for annual log-recruitment residuals. Boundary recruitment states are controlled byinit.init:"free"(default) estimates first-year recruitment and additional pre-parent-SSB boundary states as fixed parameters;"latent"integrates them without an initial density or separate SD. Independent ofprocess, including"off", and of older-ageN_settings$init. Simulations condition on these states; joint uncertainty draws can vary them. See recruitment_formulas.mu_form: one-sided formula for the expected log recruitment.NULLis allowed for RW; off/IID/AR1 require an explicit formula, such as~ 1for a constant mean. Usebh()orricker()for a median stock-recruit curve with no residuals or IID/AR1 residuals. Ordinary covariates come fromdata$pop_covariatesat the youngest modeled age. See recruitment_formulas.sd: positive fixed recruitment SD, orNULLto estimate it. This is an innovation SD for RW/AR1 and a marginal SD for IID. Must beNULLwhen process is off.phi: fixed AR1 correlation in[0, 1), orNULLto estimate it. Must beNULLfor other processes. Estimated correlations are positive.
- F_settings
A list with elements:
process:"off"makes historical F equal its explicit mean formula, without F latent states, residual SDs or correlations."iid"gives independent departures from the mean log F,"rw"for departures that accumulate over time, or"ar1"for departures correlated between years and ages."cor_rw"accumulates changes over time with AR1 correlation between annual increments at neighbouring states within each fleet. It estimates one signed age correlation per active correlation group; its SD is the marginal SD of annual log-F increments.sd_form: ordinary age-based formula for log process SD, default~ 1, evaluated ondata$catch. Sharing an SD does not share latent F states. IID SDs describe residuals; RW/correlated-RW SDs describe annual increments; stationary AR1 retains its innovation-scale convention.age_breaks: optional increasing integer interval boundaries for shared actual F. Intervals include their lower boundary and exclude their upper boundary; the final boundary must bemax(ages) + 1. For ages1:6,c(1, 2, 3, 4, 5, 7)shares F at ages 5 and 6. Ages below the first boundary have exactly zero F in all years, with no F state or process density. Positive catch observations at those ages are rejected.NULLretains separate F states at every modeled age. All mean components and process SDs must agree within a shared block; refine blocks for age-varying means. Process correlation uses ordered blocks, not replicated fish ages. With fleet labels, supply a common vector or a named list covering all fleets;NULLentries retain separate states by age within that fleet.cor_form: categorical correlation groups constant within each fleet. Default~ 1shares correlations;~ fleetestimates separate values. For AR1,list(age = ~ fleet, year = ~ 1)allows different sharing by dimension. A single formula applies to both dimensions. Sharing parameters never correlates innovations between fleets. Unused groups are omitted.mu_form: an optional formula for mean-\(F\) (coefficients estimated as log-scale parameterslog_mu_f), evaluated ondata$catch. Required for"off","iid"and"ar1"; use~ 1for one estimated mean level or~ factor(age)for age-specific means. For"rw",NULLis allowed. Mean columns constant through time cancel from RW increments and are removed with a warning. Remaining columns must be identifiable from historical year-to-year differences. Ordinary F mean designs use historical support only; projection rows carry the terminal historical design by age. No future-year coefficients are fitted. With IID residuals,iid(),rw()andar1()can add random mean effects, e.g.~ factor(age) + rw(year). Temporal mean terms must have disjoint support, for example one per fleet; overlapping temporal mean terms and structured means with temporal residual processes are not enabled.mono()andlogistic()can instead describe a rising mean age curve with no residual process or with IID/AR1 residuals, e.g.~ logistic(age). Ordinary terms supply baseline F; realized F can depart from the rising mean. Static curves are not identifiable with RW/correlated-RW residuals. See catchability_curves.double_logistic()supplies WHAM's unnormalized four-parameter dome. Catch covariates must be available even where observations are missing. They must agree across biological ages pooled into a modeled plus group. The catch responseobscannot enter an F formula. With fleet labels, each fleet has its own F states; total F is their sum for survival and Z.for_fleet()gives one fleet its own formula, e.g.~ 0 + for_fleet("Trawl", ~ factor(year) + logistic(age)) + for_fleet("Gillnet", ~ factor(year) + factor(age)). Ordinary terms outside scopes share their coefficients. Each inner formula includes an intercept unless removed explicitly; avoid redundant global and scoped baselines. smooth_formulas adds penalizeds()and age-yeart2()relationships. Smooth means support"off","iid"and"ar1"F processes; the latter combinations are experimental and require mean/process separation checks.mean_ages: optional vector of ages to include in population weighted average F (F_bar) calculations. All ages used if absent.
- M_settings
A list with elements:
process:"off"uses only supplied/mean M. Otherwise"iid","rw", and"ar1"have the same meanings as for F, on selected years/age blocks.sd_form: ordinary age-based formula for log process SD, default~ 1. Evaluated ondata$pop_covariatesand required to be constant within each fitted M age block and across years. Use finer age_breaks for distinct state SDs.mu_form: optional formula for mean-\(M\) (applied on the log scale) built ondata$pop_covariates, yielding coefficientsmu_m. These enter the log-\(M\) surface directly and may therefore be positive or negative; they intentionally avoid alog_prefix to prevent automatic back-transformation when tidied. If provided together withmu_supplied, the intercept inmu_formis dropped (warning) so supplied levels act as fixed offsets.iid(),rw()andar1()support random mean effects with IID residuals or the residual process off. Use~ 0 + rw(year)to vary supplied M through time. All mean components must be constant within fitted M age blocks. With AR1 residuals, one age-defined IID random slope is also supported, e.g.~ 0 + capelin + iid(M_age_group, by = capelin). The grouping column must be constant across years at each age and thebycolumn numeric. This experimental combination retains shared actual M within blocks; see formula_effects for the model and statistical cautions.mu_supplied: optional one-sided formula giving supplied (non-estimated) \(M\), e.g.~ I(0.2)or a column reference such as~ M_assumptionstored in thedata$pop_covariatesdata.frame.age_breaks: optional integer break points used bycut_ages()to defineage_blockssharing absolute latent M states across ages. Existing specifications retain their original interpretation. Alternatively, end atmax(ages) + 1to use explicit half-open intervals as for F: with ages1:6,c(2, 5, 7)makes blocks 2–4 and 5–6. Ages outside M process blocks retain their existing mean M; they are not set to zero. When a narrower set ofage_breaksthan modeledagesis provided, process states are only estimated for ages within the specified range; ages outside this range are fixed to their mean or assumed levels. By default, blocks usecut_ages(ages[-1], unique(range(ages[-1]))), usually one block excluding the youngest age; two older ages form separate singleton blocks. This restricts how M can trade off against recruitment; it does not guarantee identifiability.first_dev_year: one historical modeled year at which M process states begin. Defaults to the second year ifNULL. Earlier M stays at its mean or supplied value; earlier years are not coupled to the first latent state. Ignored when the M process is off.mean_ages: optional vector of ages to include in population weighted average M (M_bar) calculations. All ages used if absent.
- catch_settings
A list with elements:
sd_form: formula for log observation SD for catch-at-age data. Ordinary terms and Gaussian formula_effects are supported, e.g.~ iid(age).sd_supplied: optional one-sided formula giving supplied SDs (on the natural scale of the log-observation residuals) for catch-at-age data. When provided, the intercept is removed fromsd_formso supplied SDs act as offsets.
- index_settings
A list with elements:
sd_form: formula for log observation SD for index-at-age data. Ordinary terms and Gaussian formula_effects are supported, e.g.~ survey + rw(age, by = survey).sd_supplied: optional one-sided formula giving supplied SDs (on the natural scale of the log-observation residuals) for index-at-age data. When provided, the intercept is removed fromsd_formso supplied SDs act as offsets.q_form: formula for catchability, evaluated on the index table. Ordinary~ q_blockallows any pattern across blocks;~ mono(q_block)is non-decreasing across ordered blocks. Seemono()for independent non-decreasing curves by survey. Structured effects such as~ survey + ar1(year, by = survey)allow shared annual changes across ages.~ survey + logistic(age, by = survey)fits rising selectivity curves.~ q_key + power(N, by = q_power_group)allows abundance-dependent catchability using internally calculated abundance at survey time. Missing power-group labels mean p = 1, not missing observations. Powers require the log q link. Seepower()and formula_effects.double_logistic()allows dome-shaped selectivity and multiplies baseline q after its inverse link. Penalizeds()andt2(age, year)terms instead modify the selected q-link predictor. See smooth_formulas for supported bases, grouping and penalties.for_survey()applies a formula to one named survey, e.g.~ 0 + for_survey("RV", ~ logistic(age)) + for_survey("Sentinel", ~ q_block). Effects are built on each survey's own reporting ages, then contribute zero to the predictor elsewhere.q_link:"log"(default) allows any positive catchability;"logit"restricts catchability to between zero and one. Use the logit link only when the survey index has an absolute abundance scale and this restriction is scientifically appropriate. Relative indices or surveys affected by herding may need the log link. The same formula and optionalmono()terms work with either link. With"logit", coefficients anddqact on logit-q rather than log-q; covariate effects are no longer constant multipliers of q. This is a restriction, not a prior favouring a value near one. q can approach a boundary with large coefficients and SEs.
- proj_settings
Optional list with elements:
n_proj: number of years to project (defaultNULLdisables projections).n_mean: number of recent historical years (default3) used to average each weight type and maturity by age. Catch/index observations in projection years are missing. Other columns are copied from the last available year in each table.F_mult: multiplier of terminal historical F (default1, status quo). Can be a value of length 1 or length =n_proj. When it is a vector of length one, that multiplier is recycled across all projection years.catch: total annual catch weight, in catch-number times mean-weight units (e.g. tonnes for thousand fish and kg). Supply one value recycled across years, orn_projvalues. Specify eithercatchorF_mult, not both.catch = "observed"is restricted tofit_hindcast()evaluation; it is not a forecast input for an ordinary fit. See tinyAM-model for the catch solver and feasibility safeguards.
- interval
Level in
(0, 1)to use to generate confidence intervals, where applicable; default0.95.- add_osa_res
Logical; add one-step-ahead residuals using
"oneStepGaussianOffMode"? To select another method after fitting and retain its results, useadd_osa_residuals().- silent
Logical; if
TRUE, disables tracing information.- start_par
Optional fitted
tam_fitor list of starting parameter values, matching the structure returned bymake_par(). Useful for warm starts or retrospective runs where parameter estimates from a previous fit are reused to improve convergence and speed. Non-matching or missing entries are ignored. Fitted-object starts require the current stored parameter modes; regenerate older saved fits. Catchability coefficients are omitted ifq_linkchanges.- grad_tol
Numeric tolerance passed to
check_tam()when evaluating the fitted object's gradients. Defaults to1e-2.
Value
A tam_fit list with components:
call: matched call.
dat: data list returned by
prepare_tam().obj: RTMB
ADFunobject.opt:
[stats::nlminb()]optimization result.rep: list from
obj$report().sdrep:
RTMB::sdreport()result.fixed_par: fixed parameter estimates in a tidy format (see
tidy_par()).random_par: list of random parameter estimates in a tidy format (see
tidy_par()).is_converged: Did numerical checks pass? (see
check_tam())diagnostics: Numerical, structural and advisory checks from
check_tam().obs_pred:
obs$catchandobs$indexdata augmented with predicted values, parameter estimates, and standardized residuals (seetidy_obs_pred()).pop: A collection of population summaries in tidy format (see
tidy_pop()).rec: Recruitment predictions and residuals, with parent pairs and curve tables for stock-recruit models (see
tidy_recruitment()).formula_effects: When Gaussian q, F/M mean or observation SD formula terms are present, signed effect levels, RW increments and numeric-by contributions for reporting and dashboards.
Details
Builds data with prepare_tam() and initial parameters with make_par().
RTMB integrates random effects using the Laplace approximation; nlminb
optimizes the remaining fixed effects. mono() increments dq and dmu_f have a zero
lower bound, as do initial-abundance decrements d_n0. Their SEs remain on the increment scale, but symmetric Wald
intervals are only local approximations at a boundary.
Random-effect blocks are chosen from the active processes and Gaussian
formula effects. Initial abundance and recruitment boundaries are integrated
only when their respective init setting is "latent"; see tinyAM-model
and recruitment_formulas. make_par() describes the parameter layout.
A warning is issued if the number of random effects exceeds 1.5 times the number of observed data points (rough identifiability check).
Parameters without likelihood support are removed or fixed: examples include constant mean directions canceled by RW increments, RW SDs with no increments, and AR1 correlations on singleton axes. The latent states themselves retain the conventions in tinyAM-model.
is_converged summarizes optimizer, gradient and uncertainty checks. Also inspect
opt$convergence and opt$message; a passing numerical check does not
establish that the data identify every biological component.
Fitting also warns about sparse catchability effects and wide formula-effect
uncertainty. These advisories are retained by check_tam() and do not change
the likelihood, parameter constraints or numerical convergence criteria.
Omitted entries in a settings list use package defaults; update() instead
retains the original fit's entries. Use explicit NULL to remove an optional
formula or supplied offset. Unknown or duplicate entry names are errors.
Supply spawning timing in data$maturity$spawn_time; see prepare_tam().
Examples
fit <- fit_tam(
cod_obs,
years = 1983:2024,
ages = 2:14,
N_settings = list(process = "iid", init = "exp"),
F_settings = list(process = "rw", mu_form = NULL),
M_settings = list(process = "off", mu_supplied = ~ I(0.3)),
catch_settings = list(sd_form = ~ 1),
index_settings = list(sd_form = ~ 1, q_form = ~ q_block),
silent = TRUE
)
fit
#> Call:
#> fit_tam(data = cod_obs, years = 1983:2024, ages = 2:14, N_settings = list(process = "iid",
#> init = "exp"), F_settings = list(process = "rw", mu_form = NULL),
#> M_settings = list(process = "off", mu_supplied = ~I(0.3)),
#> catch_settings = list(sd_form = ~1), index_settings = list(sd_form = ~1,
#> q_form = ~q_block), silent = TRUE)
#>
#> Objective: 1138.33
#> ✔ Numerical convergence: passed
#> ✔ Optimizer: 0 (relative convergence (4))
#> ✔ Max |gradient|: 0.00143 adjusted; 0.00143 raw (tolerance 0.01)
#> ✔ Positive-definite Hessian: yes
#> ✔ Model structure: no redundant model terms detected
#>
#> Advisory findings (2):
#> ℹ Some age/series groups have standardized residual SDs outside 0.5 to 1.5;
#> fitted-state shrinkage also affects spread.
#> ℹ Some age/series groups have absolute adjacent-year residual correlation above
#> 0.5.
#>
#> Coefficients:
#> Estimate Lower 95% Upper 95% Std. Error SE scale
#> r0 [year=1983] [age=2] 485,911 390,954 603,932 0.111 log
#> sd_r 0.497 0.367 0.674 0.155 log
#> sd_f 0.493 0.430 0.566 0.070 log
#> sd_n 0.595 0.531 0.667 0.059 log
#> sd_catch ((Intercept)) 0.381 0.301 0.484 0.121 log
#> sd_index ((Intercept)) 0.428 0.372 0.493 0.071 log
#> q ((Intercept)) 1.751 1.245 2.463 0.174 log
#> q (q_block3) 1.484 1.142 1.929 0.134 log
#> q (q_block4) 1.477 1.083 2.013 0.158 log
#> q (q_block5) 1.284 0.910 1.811 0.176 log
#> q (q_block6-20) 1.002 0.710 1.416 0.176 log
#>
#> Terminal year (2024) estimates:
#> Estimate Lower 95% Upper 95% Std. Error SE scale
#> abundance 259,302 187,890 357,854 0.164 log
#> recruitment 39,314 18,416 83,927 0.387 log
#> ssb 180,686 132,681 246,060 0.158 log
#> F_bar 0.028 0.018 0.042 0.210 log
#> M_bar 0.300 0.300 0.300 0.000 log
#>
#> SE scale: reported = same scale as estimate; logit = log-odds.
#> A small log-scale SE approximates CV (0.10 is about 10%); use CIs for large SEs.
head(fit$pop$ssb)
#> year est lwr upr se se_scale is_proj unit spawn_time
#> 1 1983 374324.2 300052.6 466980.2 0.1128411 log FALSE tonnes 0
#> 2 1984 382659.2 308378.0 474832.9 0.1101134 log FALSE tonnes 0
#> 3 1985 386083.0 306118.9 486935.2 0.1184096 log FALSE tonnes 0
#> 4 1986 468682.0 364325.2 602930.5 0.1285113 log FALSE tonnes 0
#> 5 1987 407237.1 319341.0 519325.8 0.1240512 log FALSE tonnes 0
#> 6 1988 474914.6 367434.9 613833.7 0.1309152 log FALSE tonnes 0
#> timing
#> 1 Start of year
#> 2 Start of year
#> 3 Start of year
#> 4 Start of year
#> 5 Start of year
#> 6 Start of year
## Fit with projections (status quo F)
fit2 <- update(fit,
proj_settings = list(n_proj = 3, n_mean = 3, F_mult = 1)
)
if (interactive()) {
fits <- list("without_proj" = fit, "with_proj" = fit2)
vis_tam(fits)
}