tinyAM (tiny assessment model; aka TAM) is an R package for fitting age-structured stock assessment models using RTMB.
The package is designed to be flexible, modular, and lightweight, with an emphasis on making alternative model structures and process assumptions easy to specify, inspect, simulate, and compare.
The development version can be installed from GitHub:
# install.packages("pak")
pak::pak("PaulRegular/tinyAM")Dashboard R packages install automatically. Rendering requires Pandoc, which is included with RStudio.
See the package website and cheatsheet for guidance and examples.
Assessment models express hypotheses about where unexplained variation arises: recruitment, abundance deviations, fishing mortality, natural mortality, catchability or observations. tinyAM lets you change one or more assumptions and examine their consequences using the same diagnostic, simulation and forecasting tools.
“Tiny” refers to a small modelling vocabulary: complexity comes from combining understandable components, rather than choosing among many special-purpose models.
The required tables are catch, index, weight and maturity;
pop_covariates is optional. The Northern cod example shows all five:
library(tinyAM)
print(cod_obs, n = 2)
── tinyAM data ─────────────────────────────────────────────────────────────────────────────────────
── catch: 1512 rows, 6 columns ──
Years 1954–2025; ages 0–20
year age obs unit F_y_block F_a_block
1 1954 0 NA thousand fish 1954-1991 0-1
2 1955 0 NA thousand fish 1954-1991 0-1
⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮
1511 2024 20 0 thousand fish 2023-2025 18-20
1512 2025 20 0 thousand fish 2023-2025 18-20
── index: 1512 rows, 7 columns ──
Years 1954–2025; ages 0–20
year age obs survey samp_time q_block unit
1 1954 0 NA Fall 2J3KL 0.8 0 thousand fish
2 1954 1 NA Fall 2J3KL 0.8 1 thousand fish
⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮
1511 2025 19 0 Fall 2J3KL 0.8 6-20 thousand fish
1512 2025 20 0 Fall 2J3KL 0.8 6-20 thousand fish
── weight: 1512 rows, 4 columns ──
Years 1954–2025; ages 0–20
year age obs unit
1 1954 0 2.965967e-04 kg
24 1955 0 2.965967e-04 kg
⋮ ⋮ ⋮ ⋮ ⋮
1631 2024 20 1.409146e+01 kg
1654 2025 20 1.369174e+01 kg
── maturity: 1512 rows, 3 columns ──
Years 1954–2025; ages 0–20
year age obs
1 1954 0 0
2 1955 0 0
⋮ ⋮ ⋮ ⋮
1711 2024 20 1
1712 2025 20 1
── pop_covariates: 1512 rows, 4 columns ──
Years 1954–2025; ages 0–20
year age allometric_M_assumption M_a_block
1 1954 0 4.9990199 0-1
2 1955 0 4.9990199 0-1
⋮ ⋮ ⋮ ⋮ ⋮
1511 2024 20 0.1872682 8-20
1512 2025 20 0.1889190 8-20
The four required tables have numeric year, age and obs columns.
obs contains catch numbers, survey indices, individual weights or proportions
mature. Index rows also identify survey and samp_time: 0.8 means sampling
eight-tenths of the way through the year. q_block groups ages sharing
catchability in the example.
Catch covariates explain F means, F process SDs/correlation groups and catch
observation SDs; index covariates explain q and index SDs. The optional
pop_covariates table supplies recruitment, N and M predictors, including
process-SD groups and supplied M; it needs a complete year–age grid without obs.
Keep weights in weight, and maturity and optional spawn_time in maturity.
An optional weight$type column separates complete stock and catch weights:
stock weights determine biomass/SSB, catch weights determine yield
and catch targets. Untyped or stock-only weights serve both purposes. Each
type must cover the same biological year-age grid. Optional catch$fleet adds
separate fisheries on one shared population: supply a complete year–age grid
per fleet, with missing observations as NA. Catch weights may be shared or
fully fleet-specific using weight$fleet; stock weights have no fleet label.
See input help.
dat <- cod_obs # replace with your own named list of tables
check_tam_data(dat) # validate before fitting
# dat <- as_tam_data(dat) # optional class for compact printingCatch in thousand fish and weight in kg give biomass and SSB in tonnes.
Optional unit columns label values; they do not convert them. Survey units
can differ. Catch/index NA values and zeros are excluded from the lognormal
likelihood; weights and maturities must be complete. No missing-observation
imputation or zero-count model is provided.
See ?check_tam_data
for input requirements and
?cod_obs for
details on the sources and preparation of the supplied Northern cod data.
These are illustrative hypotheses, not package defaults or recommended stock settings. The baseline lets recruitment and fishing mortality vary while holding natural mortality at 0.3 per year:
base <- fit_tam(
dat, years = 1983:2024, ages = 2:14,
N_settings = list(process = "off", init = "exp"),
rec_settings = list(process = "rw"),
F_settings = list(process = "ar1", mu_form = ~ 1),
M_settings = list(process = "off", mu_supplied = ~ I(0.3)),
catch_settings = list(sd_form = ~ 1),
index_settings = list(sd_form = ~ 1, q_form = ~ mono(q_block))
)
N_iid <- update(base, N_settings = list(process = "iid"))
M_ar1 <- update(base, M_settings = list(
process = "ar1", age_breaks = c(2, 4, 6, 8, 14)
))Partial updates retain omitted settings. Here the alternatives add independent
cohort-survival variation or temporally correlated M shared within age blocks.
The age range ages = 2:14 uses age 14 as the terminal plus group.
Other possibilities include:
| Component | Example options | Guidance |
|---|---|---|
| Recruitment | rec_settings: "rw"; "iid" with mu_form = ~ bh(ssb); "off" with ~ factor(year) |
Recruitment: Beverton–Holt/Ricker curves support IID/AR1 residuals or deterministic recruitment, not RW residuals. |
| Cohort survival | N_settings: "off", "iid", "rw", "ar1"; mu_form = ~ temperature |
Population model: apparent gains/losses cannot separate migration from mortality. |
| Fishing mortality | F_settings: "off", "iid", "rw", "ar1", "cor_rw"; sd_form = ~ age_group |
Settings: deterministic F requires an explicit mean. Optional age_breaks share actual F and can leave younger ages unfished; "cor_rw" correlates annual changes across states. |
| Natural mortality | M_settings: "off", "iid", "rw", "ar1"; sd_form = ~ age_group |
Settings: M can be supplied or estimated; age blocks share actual M. |
| Mean F and catchability | ~ age_group, ~ logistic(age), ~ mono(age); q also supports iid(), rw(), ar1() effects |
Curves and formula effects: supported combinations have identifiability safeguards; q_link = "logit" bounds q between zero and one. |
| Observation SD | sd_form = ~ age_group |
Formula effects: formulas modify log-SD, not observation covariance. |
Recruitment boundary states are fixed by default; rec_settings$init = "latent"
integrates them without an initial density. For abundance-dependent catchability,
use power(N, by = q_key) in q_form with the log link.
With fleet catches, F is estimated separately for each fleet and summed for population survival. A single formula can give fleets different selectivity curves and surveys different q relationships:
# Settings for your fleet/survey names and complete explanatory grids
F_settings <- list(process = "off", mu_form = ~ 0 +
for_fleet("Trawl", ~ factor(year) + logistic(age)) +
for_fleet("Gillnet", ~ factor(year) + double_logistic(age)))
index_settings <- list(q_form = ~ 0 +
for_survey("RV", ~ logistic(age)) +
for_survey("Sentinel", ~ s(age, k = 4)))Scopes also support ordinary interactions. Double-logistic curves follow WHAM's four-parameter product; smooths use jointly estimated mgcv penalties. Flexible F means can be deterministic or have IID/AR1 residuals. Their separation can be weak; intrinsic F random walks cannot identify these mean shapes. Shared correlation parameters do not correlate innovations between fleets; observation covariance and time-varying structural closures remain unsupported.
check_tam(base, N_iid, M_ar1)
vis_tam(base, N_iid, M_ar1)check_tam() separates numerical checks from structural concerns and
statistical advisories. Convergence alone does not establish that the data
identify every component.
Inspect each check before interpreting or ranking an alternative.
Use plot_model(base, N_iid, M_ar1) or the dashboard's opening Model structure
page to inspect process switches and formulas. Its second tab shows function calls. The
structure guide
links contrasting diagrams to the model equations.
In the dashboard, start with Fisheries and Surveys to inspect fits and residuals. Plots start with retained OSA residuals when available, otherwise standardized residuals; the dropdown selects either type explicitly. Fisheries lists each fleet, then Aggregate fishery; without fleets, the aggregate page contains the single-fishery diagnostics. Review Tables → Diagnostics and Parameters for checks, uncertainty and process deviations before interpreting Trends for abundance and mortality. Residuals use fitted states and can be affected by shrinkage.
Test forecasting skill by repeatedly withholding terminal years:
hind <- fit_hindcast(base, N_iid, M_ar1, folds = 5)
hind
vis_tam(hind, model = "base")The summary shows Mohn’s rho (retrospective change; ideally near zero), survey
log-RMSE (lower is better), MASE (below one beats holding the last observation
constant), and scored/failed-fold counts. Compare
validation coverage before comparing scores; failed fits are retained in
hind$fold_diagnostics, not scored.
By default, each one-year forecast conditions on actual aggregate catch.
Held-out surveys and catch compositions test prediction; matching the imposed
catch total is not forecast skill. Use proj_settings = list(F_mult = 1) for
status-quo F instead. See hindcast definitions.
AIC() and cAIC() complement predictive checks for numerically acceptable
models fitted to the same observations. Intrinsic random walks and
unpenalized initial states need particular care; read the
information-criterion assumptions
before interpreting rankings.
- Tidy results:
tidy_tam(base, N_iid, M_ar1)collects population, parameter and observation tables. Confidence limits use the reported scale;se_scaleidentifies the SE scale. - Residuals:
residuals(base)extracts standardized residuals. To calculate and retain OSAs for dashboards, usebase <- add_osa_residuals(base, method = "oneStepGeneric"). Calculation can be expensive and does not refit the model. - Population shortcuts:
get_ssb(base),get_recruitment(base),get_fbar(base)andget_mbar(base)select annual summaries;get_f(base)andget_m(base)select age-specific tables. - Simulation and projections explore model behavior and future scenarios.
- Model equations explain initialization, process scales, boundaries and spawning-time SSB.
