Skip to contents

Why influence diagnostics?

CPUE standardisation attempts to distinguish changes in abundance from changes in fishing practice, location, season, vessel composition, and other explanatory variables. A fitted model may estimate these relationships well without making it obvious why the standardised index differs from the nominal series.

The coefficient-distribution-influence (CDI) framework combines the fitted effect of a term with changes in the sampled distribution of that term through the focus variable, which is usually year (Bentley et al. 2012). Spatial and spatiotemporal versions can similarly show whether changes in sampled location move observations among areas of different predicted abundance (Hsu et al. 2022).

influ2 represents these results with one model-neutral S3 class. Each backend extracts model components and joint uncertainty, while the calculation, storage, summary, and plotting layers remain shared.

Influence explains what drives an index; it does not establish that the model fits the observations adequately. The Residual diagnostics article checks that complementary question using the same simulated lobster data.

The Model comparison article summarises native likelihood and Bayesian criteria across model types, including conditional AIC where supported. Those scores assess fitted/predictive performance, not the robustness of the abundance index by themselves.

A simulated lobster CPUE example

The main examples use the simulated lobsters_per_pot data supplied with the package. They contain 5,049 pot records spanning 2000 to 2017, with uneven annual sample sizes and deliberate gaps in year-month coverage. The response is the number of lobsters caught per pot, and the explanatory variables are month, depth in metres, and soak time in hours.

The simulation makes three changes in fishing practice visible. Sampling shifts around 2004–2005 from a March-centred season towards a September-centred season. The specified seasonal catch effect is higher in September. Fishing moves deeper during 2007–2011, into depths with lower expected catch in the simulation. From about 2012, longer soak times become more common and increase expected catch per pot. These changes overlap a known annual effect that fluctuates around a gradual decline.

This is a deliberately constructed teaching example. The seasonal, depth, and soak-time relationships are specified to illustrate confounding, not estimated relationships from a real lobster fishery. Catch has negative-binomial variation, and every value comes from the package’s fixed-seed simulator; no commercial fishing records are included. The known annual effect is retained in the dataset’s simulation attribute for the truth check following the refitted step plot below.

library(influ2)
data(lobsters_per_pot)

dim(lobsters_per_pot)
#> [1] 5049    5
head(lobsters_per_pot)
#>   lobsters year month    depth     soak
#> 1        3 2000    01 18.68397 25.09660
#> 2        1 2000    01 22.21965 47.60876
#> 3        3 2000    01 23.77189 22.54391
#> 4        1 2000    01 30.98419 22.42277
#> 5        2 2000    01 15.62055 24.73538
#> 6        1 2000    01 13.58122 23.45524

Checking data completeness

Before fitting a model, plot_data_extent() shows which measurements are available in each year. The supplied lobster measurements are complete, so we make a separate demonstration copy with deliberately missing depth and soak-time values. This illustrates a recording system in which soak time was initially unavailable, then partly recorded, and eventually complete. These added gaps are illustrative, not a feature of the original simulation. All models below still use the unchanged lobsters_per_pot dataset.

coverage_example <- lobsters_per_pot
example_year <- as.integer(as.character(coverage_example$year))
record_number <- seq_len(nrow(coverage_example))

# No soak-time records before 2005, then every second record missing to 2009.
coverage_example$soak[example_year < 2005] <- NA_real_
coverage_example$soak[
  example_year >= 2005 & example_year < 2010 & record_number %% 2 == 0
] <- NA_real_

# Every fourth depth record is missing before 2010.
coverage_example$depth[
  example_year < 2010 & record_number %% 4 == 0
] <- NA_real_

plot_data_extent(
  coverage_example,
  xvar = "year",
  yvar = c("lobsters", "depth", "soak")
) +
  scale_x_discrete(labels = c(
    lobsters = "Lobsters per pot", depth = "Depth", soak = "Soak time"
  )) +
  labs(y = "Year")
Annual completeness of lobster counts, depth, and soak time, with blank cells for the earliest soak-time records and increasing covariate availability.

Measurement completeness by year in a demonstration copy of the lobster data with deliberately added missing depth and soak-time values. Dark bubble area represents the proportion of existing records with a non-missing value; blank cells indicate none are available. Lobster counts, including zeros, remain fully recorded.

The denominator is the number of records already present in each year, not the number of pot lifts that could have been sampled. A zero lobster count is a valid observation and counts as present; only NA is missing. Cells with zero completeness are left blank; positive completeness is shown by the area of a dark bubble, with no background markers. This display does not assess measurement accuracy or show how many records would survive joint complete-case filtering across several covariates.

Sampling patterns

The changing distribution of records by year and month is visible before a model is fitted. Larger bubbles represent more pot records. With no fill argument, plot_bubble() uses its default purple palette.

plot_bubble(
  df = lobsters_per_pot,
  group = c("year", "month")
) +
  labs(x = "Month", y = "Year")
Sampling effort by year and month, using the default purple bubble style.

Sampling effort by year and month, using the default purple bubble style.

Mapping month to colour gives a second view of the same sampling pattern.

plot_bubble(
  df = lobsters_per_pot,
  group = c("year", "month"),
  fill = "month"
) +
  labs(x = "Month", y = "Year") +
  theme(legend.position = "none")
Sampling effort by year and month, with a rainbow palette distinguishing months.

Sampling effort by year and month, with a rainbow palette distinguishing months.

Depth and soak-time coverage also change through time.

covariates <- tidyr::pivot_longer(
  lobsters_per_pot,
  cols = c("depth", "soak"),
  names_to = "covariate",
  values_to = "value"
)

ggplot(
  covariates,
  aes(x = year, y = value, group = year)
) +
  geom_boxplot(outlier.alpha = 0.08, linewidth = 0.25) +
  facet_wrap(~covariate, scales = "free_y", ncol = 1) +
  scale_y_continuous(
    limits = c(0, NA),
    expand = expansion(mult = c(0, 0.05))
  ) +
  labs(x = "Year", y = NULL) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))
Observed depth and soak-time distributions by year.

Observed depth and soak-time distributions by year.

GLM

A Poisson GLM provides a transparent starting point. This is an example model, not a recommendation that Poisson variation is adequate for these data.

lobster_glm <- glm(
  lobsters ~ year + month + poly(depth, 3) + poly(soak, 3),
  family = poisson(link = "log"),
  data = lobsters_per_pot
)

glm_diagnostic <- influ(lobster_glm, focus = "year")
glm_diagnostic
#> <influ_diag>
#>   Backend:     glm
#>   Response:    single poisson (log)
#>   Focus:       year
#>   Terms:       4
#>   Focus levels:18
#>   Uncertainty: analytic covariance
#>   Retained:    summary
summary(glm_diagnostic)
#> Influence diagnostic summary
#>   Backend: glm
#>   Family:  single poisson (log)
#>   Focus:   year
#> 
#>            term   component maximum_absolute_link_influence level_at_maximum
#>           month conditional                       0.4323325             2000
#>            year conditional                       0.3508885             2002
#>  poly(depth, 3) conditional                       0.2664725             2008
#>   poly(soak, 3) conditional                       0.2384326             2014

The influence plot shows how changes in sampled month, depth, and soak time move the annual standardised series. The year term is retained in the object, but omitted from this plot because its within-year composition is the focus, not a sampling-distribution effect.

plot(
  glm_diagnostic,
  type = "influence",
  term = c("month", "poly(depth, 3)", "poly(soak, 3)")
)
GLM influence ratios for the changing lobster covariate distributions.

GLM influence ratios for the changing lobster covariate distributions.

The same object contains nominal and standardised indices. Here, nominal means the observed annual arithmetic mean of lobsters per pot, including zero catches, without adjusting for month, depth, or soak time. Each pot record has equal weight in this example; supplying weights to influ() instead gives a weighted annual mean. The standardised index is the exponentiated, centred year effect from the fitted log-link model. It is a relative index, not an estimate in lobsters per pot, so the two series appear in separate panels labelled response and ratio, respectively.

plot(glm_diagnostic, type = "index")
Observed annual mean lobsters per pot (nominal, response scale) and the relative Poisson-GLM-standardised index (ratio scale).

Observed annual mean lobsters per pot (nominal, response scale) and the relative Poisson-GLM-standardised index (ratio scale).

The model-neutral overall and trend metrics reproduce Bentley’s definitions. The overall metric is the mean absolute link-scale influence transformed back to a proportional scale. The trend metric is the fitted change per ordered focus level, also transformed to a proportional scale.

subset(
  influ_metrics(glm_diagnostic),
  term != "year",
  select = c(term, metric, estimate)
)
#>             term  metric     estimate
#> 3          month overall  0.200153750
#> 4          month   trend  0.037814471
#> 5 poly(depth, 3) overall  0.116432819
#> 6 poly(depth, 3)   trend -0.004176475
#> 7  poly(soak, 3) overall  0.140917166
#> 8  poly(soak, 3)   trend  0.024185229

Reading a CDI plot

A CDI plot aligns the fitted term effect above its observed distribution, with the resulting annual influence beside it. By default, the top panel centres the term on the same weighted reference distribution as the influence calculation. For this log-link model, it shows relative month effects on a logarithmic axis, with one as the reference. A value of 1.2 represents a 20% higher monthly contribution to expected catch than the reference, holding the other model components fixed. No month is singled out merely because it is the model’s first factor level.

The default reference uses the observations and any supplied weights. Supplying reference_data and, optionally, reference_weights changes both the influence reference and the CDI centring to that explicit distribution. The bubbles continue to show the observed sampling distribution.

For encounter models with a logit link, the upper panel is labelled “Effect (log-odds)” and remains centred on zero by default. Positive log-link models retain “Relative Effect”. Short category labels are horizontal for both fixed and random effects. Label spacing adapts to the rendered panel width and text size. Both panels label every category when they fit; otherwise, they label every second, third, or subsequent category, starting at the first. The last category is not forced into the labels if it falls outside that regular sequence. There is no fixed label-count limit: wider outputs can show more labels. All coefficients, bubbles, and category ticks remain plotted.

# Centred relative month effects, with 95% confidence intervals.
plot(glm_diagnostic, type = "cdi", term = "month")

# The same centred effects in additive log units.
plot(
  glm_diagnostic, type = "cdi", term = "month",
  coefficient_scale = "link"
)

# Original model coding: log effects relative to month 1 for this GLM.
plot(
  glm_diagnostic, type = "cdi", term = "month",
  coefficient_reference = "model"
)

The interval calculation includes uncertainty in the estimated centre and its covariance with each term effect. The default interval covers 95%; set probs when calling influ() to choose other bounds. The model-reference option changes only the top panel, leaving the composition and influence panels unchanged.

The display scale follows the component’s link. Log-link and lognormal components use relative effects; identity-link components use additive effects. Logit, probit, and complementary-log-log components remain in their labelled link units, centred on zero. Their top panels are not catch multipliers or changes in encounter probability.

A common prediction-grid reference

Observed-data standardisation is the default. For comparisons among fleets, areas, or models, an explicit prediction grid prevents differences in their observed samples from silently changing the reference distribution. This grid crosses every year and month at common depth and soak values.

lobster_reference <- expand.grid(
  year = levels(lobsters_per_pot$year),
  month = levels(lobsters_per_pot$month),
  depth = median(lobsters_per_pot$depth),
  soak = median(lobsters_per_pot$soak)
)
lobster_reference$year <- factor(
  lobster_reference$year,
  levels = levels(lobsters_per_pot$year)
)
lobster_reference$month <- factor(
  lobster_reference$month,
  levels = levels(lobsters_per_pot$month)
)

glm_grid_diagnostic <- influ(
  lobster_glm,
  focus = "year",
  reference_data = lobster_reference
)
glm_grid_diagnostic$metadata[c("reference", "n_reference")]
#> $reference
#> [1] "prediction_grid"
#> 
#> $n_reference
#> [1] 216

Reference weights can be supplied with reference_weights. They may be a numeric vector or the name of a column in reference_data. This makes the standardisation estimand explicit, reproducible, and independent of accidental sample imbalance.

GAM

The mgcv backend works with parametric terms and smooths (Wood 2017). Here, negative-binomial variation is combined with smooth depth and soak effects.

lobster_gam <- mgcv::gam(
  lobsters ~ year + month + s(depth, k = 5) + s(soak, k = 5),
  family = mgcv::nb(),
  method = "REML",
  data = lobsters_per_pot
)

gam_diagnostic <- influ(lobster_gam, focus = "year")
summary(gam_diagnostic)
#> Influence diagnostic summary
#>   Backend: gam
#>   Family:  single negative_binomial (log)
#>   Focus:   year
#> 
#>      term   component maximum_absolute_link_influence level_at_maximum
#>     month conditional                       0.4318174             2000
#>      year conditional                       0.3573037             2002
#>  s(depth) conditional                       0.2447380             2008
#>   s(soak) conditional                       0.2368403             2014
plot(
  gam_diagnostic,
  type = "influence",
  term = c("month", "s(depth)", "s(soak)")
)
GAM influence ratios for month, depth, and soak time.

GAM influence ratios for month, depth, and soak time.

This is the same influ_diag interface as the GLM. No plotting code needs to know that two of the terms are smooths.

glmmTMB

glmmTMB adds mixed effects, negative-binomial models, hurdle models, and zero-inflation (Brooks et al. 2017). The example treats month as a random intercept.

lobster_glmmTMB <- glmmTMB::glmmTMB(
  lobsters ~ year + poly(depth, 2) + poly(soak, 2) + (1 | month),
  family = glmmTMB::nbinom2(),
  data = lobsters_per_pot
)

glmmTMB_diagnostic <- influ(lobster_glmmTMB, focus = "year")
summary(glmmTMB_diagnostic)
#> Influence diagnostic summary
#>   Backend: glmmTMB
#>   Family:  single negative_binomial (log)
#>   Focus:   year
#> 
#>            term      component maximum_absolute_link_influence level_at_maximum
#>  random_effects random_effects                       0.4208714             2000
#>            year    conditional                       0.3507567             2002
#>  poly(depth, 2)    conditional                       0.2498206             2008
#>   poly(soak, 2)    conditional                       0.2345822             2014
plot(glmmTMB_diagnostic, type = "components")
glmmTMB fixed- and random-effect influence ratios.

glmmTMB fixed- and random-effect influence ratios.

Fixed effects use the joint maximum-likelihood covariance. The month random effect uses its fitted conditional modes, with uncertainty propagated from their joint conditional latent covariance and labelled separately. For hurdle and zero-inflated fits, influ() also calculates population-level unconditional-mean influence using the joint covariance of both components.

brms

The Bayesian backend uses genuine joint posterior draws, but projects each draw directly to the much smaller focus-by-term diagnostic. It never constructs an observation-by-draw-by-term array (Bürkner 2017). A model for the same lobster data can be fitted as follows:

lobster_brms <- brms::brm(
  lobsters ~ year + (1 | month) + s(depth, k = 3) + soak,
  family = brms::negbinomial(),
  data = lobsters_per_pot,
  chains = 4,
  cores = 4,
  iter = 5000,
  warmup = 2000,
  seed = 20260905,
  control = list(adapt_delta = 0.99, max_treedepth = 12),
  file = "fit2",
  file_refit = "on_change"
)

The vignette uses the fitted model shipped with its source, rather than running MCMC whenever the documentation is built.

lobster_brms <- readRDS(system.file(
  "extdata", "brms-fixtures", "fit2.rds", package = "influ2"
))
brms_diagnostic <- influ(
  lobster_brms,
  focus = "year",
  ndraws = 250,
  retain = "summary"
)
summary(brms_diagnostic)
#> Influence diagnostic summary
#>   Backend: brms
#>   Family:  single negative_binomial (log)
#>   Focus:   year
#> 
#>             term               component maximum_absolute_link_influence
#>            month conditional:group_level                       0.4210921
#>             year             conditional                       0.3513136
#>             soak             conditional                       0.2420369
#>  s(depth, k = 3)      conditional:smooth                       0.2378273
#>  level_at_maximum
#>              2000
#>              2002
#>              2014
#>              2008
plot(brms_diagnostic, type = "components")
brms population-level year and soak-time, month group-level, and depth-smooth influence ratios.

brms population-level year and soak-time, month group-level, and depth-smooth influence ratios.

Population-level, group-level, and smooth contributions all preserve posterior dependence during calculation. The default object retains interval summaries; retain = "derived_draws" retains only the compact diagnostic draws. The soak-time term is included because longer soak times are a known source of confounding in this simulation, alongside seasonal and depth changes.

The same posterior diagnostic can be displayed as a complete Bayesian CDI plot. The top panel shows relative month effects centred on the observed monthly distribution; the lower panels align that distribution with the resulting yearly influence. Each joint posterior draw is centred before it is exponentiated. Points are posterior means of those relative effects, and bars are 95% credible intervals. This preserves dependence between each month and the estimated reference, without retaining the full posterior array in the diagnostic. The first month therefore has an estimated effect and an interval, just like every other month.

plot(
  brms_diagnostic,
  type = "cdi",
  term = "month",
  component = "conditional:group_level"
)
Bayesian CDI for the monthly group-level effect: centred relative month effects with 95% credible intervals (top left), observed monthly composition (bottom left), and annual influence (bottom right).

Bayesian CDI for the monthly group-level effect: centred relative month effects with 95% credible intervals (top left), observed monthly composition (bottom left), and annual influence (bottom right).

sdmTMB

The lobster data deliberately have no coordinates. Inventing coordinates from depth or soak time would create a misleading spatial example, so this example uses the Pacific cod data and mesh supplied by sdmTMB (Anderson et al. 2025). Fixed, spatial, and spatiotemporal contributions are returned in the same schema as the lobster examples.

data("pcod_2011", package = "sdmTMB")
data("pcod_mesh_2011", package = "sdmTMB")

spatial_model <- sdmTMB::sdmTMB(
  present ~ as.factor(year) + depth_scaled,
  data = pcod_2011,
  mesh = pcod_mesh_2011,
  family = binomial(),
  time = "year",
  spatial = "on",
  spatiotemporal = "iid",
  silent = TRUE
)

sdmTMB_diagnostic <- influ(
  spatial_model,
  focus = "year",
  ndraws = 100,
  seed = 1
)
summary(sdmTMB_diagnostic)
#> Influence diagnostic summary
#>   Backend: sdmTMB
#>   Family:  single binomial (logit)
#>   Focus:   year
#> 
#>                  term                 component maximum_absolute_link_influence
#>       as.factor(year)               conditional                      0.60757313
#>         spatial_field conditional:latent_fields                      0.10528749
#>          depth_scaled               conditional                      0.05400688
#>  spatiotemporal_field conditional:latent_fields                      0.03084851
#>  level_at_maximum
#>              2013
#>              2015
#>              2015
#>              2013
plot(sdmTMB_diagnostic, type = "components")
sdmTMB fixed, spatial, and spatiotemporal influence components.

sdmTMB fixed, spatial, and spatiotemporal influence components.

tinyVAST

The tinyVAST example uses a small reproducible spatial count data set. The purpose is to exercise the spatial and spatiotemporal component interface, not to represent a complete survey analysis (Thorson et al. 2025).

set.seed(2)
n <- 120
spatial_counts <- data.frame(
  x = runif(n),
  ycoord = runif(n),
  time = rep(1:4, each = n / 4)
)
spatial_counts$year <- factor(spatial_counts$time)
spatial_counts$var <- "catch"
spatial_counts$dist <- "poisson"
eta <- 0.3 + 0.08 * spatial_counts$time +
  0.3 * sin(2 * pi * spatial_counts$x)
spatial_counts$catch <- rpois(n, exp(eta))

spatial_mesh <- fmesher::fm_mesh_2d(
  spatial_counts[c("x", "ycoord")],
  n = 25
)
tiny_model <- tinyVAST::tinyVAST(
  catch ~ year,
  data = spatial_counts,
  family = list(poisson = poisson()),
  spatial_domain = spatial_mesh,
  spacetime_term = "",
  space_columns = c("x", "ycoord")
)

tinyVAST_diagnostic <- influ(
  tiny_model,
  focus = "year",
  ndraws = 100,
  seed = 1
)
summary(tinyVAST_diagnostic)
#> Influence diagnostic summary
#>   Backend: tinyVAST
#>   Family:  single poisson (log)
#>   Focus:   year
#> 
#>                  term                 component maximum_absolute_link_influence
#>                  year               conditional                       0.2437404
#>  spatiotemporal_field conditional:latent_fields                       0.0515625
#>  level_at_maximum
#>                 4
#>                 2
plot(tinyVAST_diagnostic, type = "components")
tinyVAST fixed and spatial influence components.

tinyVAST fixed and spatial influence components.

The spatial adapters propagate fixed-effect covariance and simulate spatial and spatiotemporal fields from each model’s sparse joint precision matrix. Each draw is reduced immediately to focus-level influence, so the returned object does not contain an observations-by-draws field array. Delta fields use the same joint draw for occurrence and positive components before the unconditional mean is calculated.

Comparing year-effect indices

This section compares year-effect contrasts: how the fitted year term changes between years, expressed here on a relative scale. It does not predict lobsters per pot at a specified depth and soak time. That expected-response index is calculated in CPUE indices for stock assessment below.

plot_compare() accepts a list of fitted models from different backends, or their already-calculated influ_diag objects. Reusing the diagnostics below avoids repeating model fitting, posterior sampling, or influence calculations. It can also plot a list of cpue_index() results. With method = "standardised", these contain a different, explicitly chosen quantity: expected CPUE at a common reference profile or population. The function plots whichever kind of result is supplied; it does not silently convert one into the other or mix them together.

These four lobster models use the same response, pot records, and years. They differ in both their model structure and their distribution: the GLM is Poisson, the other models are negative binomial, and their covariate and month-effect specifications differ. This is therefore a sensitivity comparison between the fitted models, not a controlled comparison of fitting software.

lobster_diagnostics <- list(GLM = glm_diagnostic)
if (has_mgcv) lobster_diagnostics$GAM <- gam_diagnostic
if (has_glmmTMB) lobster_diagnostics$glmmTMB <- glmmTMB_diagnostic
if (has_brms) lobster_diagnostics$brms <- brms_diagnostic

rescale = 1 gives each series a geometric mean of one over the common 2000–2017 period, making their relative trajectories directly comparable. Uncertainty ribbons are omitted here to keep overlapping lines readable.

plot_compare(
  lobster_diagnostics,
  labels = names(lobster_diagnostics),
  rescale = 1,
  show_probs = FALSE
) +
  labs(x = "Year", y = "Relative standardised CPUE", colour = "Model") +
  theme(legend.position = "bottom")
Annual standardised lobster CPUE indices on a common relative scale, with separate labelled lines for each fitted model.

Relative standardised lobster CPUE indices from the available GLM, GAM, glmmTMB, and brms examples. Each series has a geometric mean of one over 2000–2017; differences reflect the models’ distributions and structures as well as their estimation methods.

Set show_probs = TRUE to display the intervals already stored in the diagnostics. For these objects, they are 95% confidence or credible intervals, depending on the backend. Rescaling multiplies the estimates and interval bounds by the same display constant; it does not recalculate uncertainty in the estimated normalising constant or provide a test of differences between models.

The same function can include sdmTMB and tinyVAST models when their indices represent the same response, population, and time period on a compatible scale. Their demonstrations above use different data, so they are deliberately excluded here. For other comparisons, also check the reference distribution, component (for example, unconditional mean versus positive catch), and shared years before overlaying indices. A common plotting interface does not by itself make those quantities comparable.

Refitted step plots

A refitted step plot asks how the estimated year effect changes as terms are added to a model. Each changed specification is fitted afresh, re-estimating all its coefficients. The result therefore depends on the order in which terms are added: it describes that sequence of models, rather than allocating an order-independent share of the final result to each term.

For a simple GLM, influ_steps() can construct the sequence from the fitted formula. It starts with the year term, preserves any offsets, and then adds the remaining terms in formula order. This lobster example starts with year, then adds month, the depth polynomial, and the soak-time polynomial. All steps use the same pot records and observed reference distribution.

For the main step demonstration we fit a negative-binomial GLM, matching the overdispersed count distribution used by the simulator. The earlier Poisson GLM remains a simple introduction, but here both the coefficients and the negative-binomial dispersion parameter, theta, are estimated for every changed model specification. A reduced model can absorb omitted structure into its dispersion, so theta is not held at the full model’s estimate. This example requires the suggested package MASS.

lobster_nb <- MASS::glm.nb(
  lobsters ~ year + month + poly(depth, 3) + poly(soak, 3),
  link = log,
  data = lobsters_per_pot
)

lobster_steps <- influ_steps(
  lobster_nb,
  year = "year",
  refit = TRUE
)
lobster_steps
#> <influ_steps>
#>   Estimand: year-effect contrasts (not spatial abundance)
#>   Focus: year
#>   Steps: 4
#>   Refitted: 3
#>  step_id              label backend          status
#>        1          Year only     glm        refitted
#>        2          Add month     glm        refitted
#>        3 Add poly(depth, 3)     glm        refitted
#>        4  Add poly(soak, 3)     glm reused original

A step that exactly matches the original model or an earlier step reuses that fit. The printed status records whether each model was refitted or reused; this avoids repeating an identical fit while preserving the model comparison.

plot_step(lobster_steps)
Four sequential negative-binomial GLM panels showing centred year-effect ratios for year only, year plus month, then depth, then soak time.

Changes in the relative year effect as month, depth, and soak-time terms are added in a negative-binomial lobster GLM refitting sequence. Each changed model re-estimates its coefficients and dispersion; each panel highlights the current model with its approximate 95% confidence interval and retains the preceding models for comparison.

The sequence now exposes the changes built into the simulation. Adding month raises the early relative year effects, correcting for sampling in months with lower expected catch. Adding depth corrects the middle-period trough associated with deeper fishing. Adding soak time reduces the false late increase associated with longer soaks. These are changes after refitting all the included terms, so their size also reflects the order of the sequence.

Because the annual effect is known here, we can check the fitted contrasts against it. The code centres the simulated log year effects using the observed number of pot records in each year, matching the step diagnostic’s reference. It applies no additional display rescaling. The table reports root mean squared error (RMSE) across the 18 years on the log scale.

year_truth <- attr(lobsters_per_pot, "simulation")$year_effect
year_counts <- table(lobsters_per_pot$year)
year_truth$centred_log_effect <- year_truth$log_effect - weighted.mean(
  year_truth$log_effect,
  as.numeric(year_counts[as.character(year_truth$year)])
)

step_indices <- influ_indices(lobster_steps)
truth_check <- do.call(rbind, lapply(lobster_steps$steps$step_id, function(i) {
  fitted_years <- step_indices[step_indices$step_id == i, ]
  truth_rows <- match(fitted_years$level, as.character(year_truth$year))
  data.frame(
    step = lobster_steps$steps$label[lobster_steps$steps$step_id == i],
    rmse_log = sqrt(mean(
      (log(fitted_years$estimate) -
        year_truth$centred_log_effect[truth_rows])^2
    ))
  )
}))
knitr::kable(
  truth_check,
  digits = 3,
  col.names = c("Model step", "RMSE of log year effect"),
  caption = "Negative-binomial model agreement with the known annual effect in this simulated dataset."
)
Negative-binomial model agreement with the known annual effect in this simulated dataset.
Model step RMSE of log year effect
Year only 0.311
Add month 0.216
Add poly(depth, 3) 0.169
Add poly(soak, 3) 0.084

For this fixed simulated dataset, the fitted year contrasts move closer to the known truth as the three covariates are added. This is a teaching check of point estimates, not cross-validated predictive performance or evidence that every added term improves an index in practice. The approximate confidence bands use each negative-binomial model’s coefficient covariance matrix at its estimated dispersion. They account for count overdispersion under that model, but are not a bootstrap over dispersion estimation or model selection. In particular, reduced models deliberately omit relevant terms. This point-estimate truth check does not evaluate interval coverage.

These are centred year-effect contrasts. For the lobster log-link models, they are exponentiated year effects relative to a common reference, not area-weighted abundance indices or spatially integrated predictions. The shaded interval belongs to the current fitted model. It is not an interval for the difference between that model and the preceding one, because the models were fitted to the same observations and their estimates are dependent.

Once calculated, plot(lobster_steps) or plot_step(lobster_steps) reuses the stored summaries without fitting again. The shortcut below creates the same kind of plot directly from a fitted model, but it performs the refits each time it is called. The automatic main-formula sequence also supports simple GAMs and glmmTMB models.

plot_step(lobster_nb, year = "year", refit = TRUE)

gam_steps <- influ_steps(lobster_gam, year = "year", refit = TRUE)
plot(gam_steps)

Use an explicit named steps list when the order or model structure needs closer control. Each formula is fitted against the original model’s settings, so the sequence is visible in the code. A common reference_data grid and reference_weights may be passed to influ_steps() when the observed reference is not the intended comparison.

lobster_steps <- influ_steps(
  lobster_nb,
  year = "year",
  refit = TRUE,
  steps = list(
    "Year" = ~year,
    "Year + depth" = ~year + poly(depth, 3),
    "Year + depth + month" = ~year + poly(depth, 3) + month,
    "Year + depth + month + soak" =
      ~year + poly(depth, 3) + month + poly(soak, 3)
  ),
  reference_data = lobster_reference
)

For expensive models, particularly brms fits, supply an ordered list of models that have already been fitted. This calculates and plots their year contrasts without running MCMC again. The models should use the same response, observations, focus levels, and reference distribution. Select the intended component explicitly for models with several response components. Check the convergence of supplied fits before including them; their supplied status does not record a new convergence assessment.

brms_steps <- influ_steps(
  list(
    "Year" = brms_year_fit,
    "Year + month" = brms_month_fit,
    "Year + month + depth" = brms_depth_fit,
    "Year + month + depth + soak" = brms_soak_fit
  ),
  year = "year",
  ndraws = 250
)
plot(brms_steps)

Intervals default to 95% when step diagnostics are calculated; set probs to change their coverage. The default keep_fits = FALSE keeps the result compact. Set keep_fits = TRUE only when the fitted intermediate models are needed afterwards, because retaining those models can substantially increase the object’s size. The spatial vignette shows explicit sequences that add spatial and spatiotemporal structure while continuing to compare the fitted year effects.

CPUE indices for stock assessment

An index table and its plot are routine outputs from CPUE standardisation. Use cpue_index() to calculate the table once, then plot_index() to display it. Here we reuse the negative-binomial glmmTMB model fitted above, without refitting it.

The question is now: what is the expected number of lobsters per pot in each year, at the same depth and soak time? We explicitly choose median observed depth and a 24-hour soak. The year column is omitted from the reference data because the same profile is used in every observed year. The monthly random effect is set to zero: this is a zero-month-effect prediction on the link scale, not an average over the population of monthly random effects.

assessment_reference <- data.frame(
  depth = median(lobsters_per_pot$depth),
  soak = 24
)

lobster_cpue <- cpue_index(
  lobster_glmmTMB,
  year = "year",
  method = "standardised",
  reference_data = assessment_reference,
  units = "lobsters per pot"
)

lobster_index_table <- index_table(lobster_cpue)

Both method = "standardised" and method = "standardized" work. The calculated result contains the familiar assessment columns, plus method, distribution, and link information. The reporting columns are shown below. Mean is the estimated expected CPUE, SD is its standard error, and CV is SD / Mean. Qlower and Qupper give the pointwise 95% confidence interval. index_table() omits an unavailable Median column for this frequentist fit; it retains the genuine posterior median when using a complete brms fit. as.data.frame() still provides the full schema, including missing medians, for code that depends on those columns. None of these uncertainty columns describes variation among individual observations.

knitr::kable(
  lobster_index_table[c("Year", "Mean", "SD", "CV", "Qlower", "Qupper")],
  digits = 3,
  caption = "Standardised lobster CPUE and uncertainty at the common reference profile."
)
Standardised lobster CPUE and uncertainty at the common reference profile.
Year Mean SD CV Qlower Qupper
2000 1.689 0.240 0.142 1.278 2.231
2001 1.761 0.249 0.142 1.334 2.325
2002 1.841 0.257 0.139 1.401 2.420
2003 1.401 0.191 0.136 1.073 1.830
2004 1.586 0.216 0.136 1.215 2.070
2005 1.318 0.186 0.141 0.999 1.739
2006 1.167 0.158 0.136 0.895 1.522
2007 1.192 0.162 0.136 0.914 1.555
2008 1.183 0.165 0.139 0.900 1.554
2009 1.134 0.158 0.140 0.862 1.490
2010 1.230 0.168 0.137 0.940 1.608
2011 1.390 0.190 0.136 1.064 1.816
2012 1.217 0.170 0.140 0.925 1.601
2013 1.369 0.186 0.136 1.049 1.788
2014 1.258 0.170 0.135 0.965 1.640
2015 1.059 0.145 0.137 0.810 1.384
2016 1.090 0.147 0.135 0.837 1.420
2017 1.113 0.152 0.137 0.851 1.455

The plot uses the stored estimates and intervals. It does not fit a model or repeat the prediction calculation.

plot_index(lobster_cpue) +
  labs(x = "Year")
Annual expected lobster CPUE at a fixed reference profile, with an uncertainty ribbon and a y-axis starting at zero.

Standardised expected lobsters per pot from the negative-binomial glmmTMB model at median observed depth and a 24-hour soak, with the monthly random effect set to zero. The ribbon is a pointwise 95% confidence interval for the index, not the spread of individual pot catches.

For a relative assessment index, set rescale = 1 in cpue_index(). That also propagates uncertainty in the common normalising denominator. For a population rather than one reference profile, supply several reference rows and reference_weights; predictions are averaged on the response scale. The CPUE indices article explains these choices and the compact posterior calculation for brms.

To overlay assessment indices from several models, calculate one cpue_index() result per model using comparable reference populations, units, and random-effect targets, then pass the results to plot_compare(). That is the multi-model equivalent of plot_index(). Its executable two-model example shows the resulting plot. Passing fitted models or influ_diag objects instead retains the year-effect comparison shown earlier. In a simple additive log-link model the relative trajectories can coincide after rescaling, but that should not be assumed for models with year interactions or more complicated response structures.

cpue_index() now provides this expected-response table for all six model classes. A separate integrate_index() sums predictions times explicit cell areas, whether the model is a GLM, a GAM with a spatial smooth, a mixed model, or a specialist spatiotemporal model. See CPUE indices for an executed GLM/GAM area comparison, and the spatial article for sdmTMB and tinyVAST response indices and totals. Area integration does not automatically convert CPUE into absolute biomass; compatible density units or an explicit catchability conversion are required.

Passing annual covariance to an assessment

Annual estimates from one fitted model can share uncertainty through common coefficients, smooths, or latent effects. Passing only their SDs loses that dependence. index_vcov() retrieves the annual-index variance–covariance matrix, not the much larger model-coefficient matrix. It works for the same six backends as cpue_index() and integrate_index().

Sigma_log <- index_vcov(lobster_cpue, scale = "log", require_pd = TRUE)
Sigma_response <- index_vcov(lobster_cpue, scale = "response")
stopifnot(identical(rownames(Sigma_log), lobster_index_table$Year))

assessment_table <- data.frame(
  Year = lobster_index_table$Year,
  Index = lobster_index_table$Mean,
  LogIndex = log(lobster_index_table$Mean),
  SElog = sqrt(diag(Sigma_log)),
  row.names = NULL
)
knitr::kable(assessment_table, digits = 3,
  caption = "Unscaled annual indices and log-scale standard errors from their joint covariance. The complete matrix, not just SElog, accompanies this table.")
Unscaled annual indices and log-scale standard errors from their joint covariance. The complete matrix, not just SElog, accompanies this table.
Year Index LogIndex SElog
2000 1.689 0.524 0.142
2001 1.761 0.566 0.142
2002 1.841 0.610 0.139
2003 1.401 0.337 0.136
2004 1.586 0.461 0.136
2005 1.318 0.276 0.141
2006 1.167 0.154 0.136
2007 1.192 0.176 0.136
2008 1.183 0.168 0.139
2009 1.134 0.125 0.140
2010 1.230 0.207 0.137
2011 1.390 0.329 0.136
2012 1.217 0.196 0.140
2013 1.369 0.314 0.136
2014 1.258 0.229 0.135
2015 1.059 0.057 0.137
2016 1.090 0.086 0.135
2017 1.113 0.107 0.137

Here LogIndex is the log of the fitted expected-response estimate. It is not a posterior mean of log indices or a bias-corrected estimate. The diagonal of Sigma_log contains log-index variances; the off-diagonal entries describe covariance between annual estimation errors. Correlation rescales those entries and is often easier to read.

patchwork::wrap_plots(
  plot(lobster_cpue, type = "covariance") + labs(subtitle = NULL),
  plot(lobster_cpue, type = "correlation") + labs(subtitle = NULL),
  nrow = 1
)
Two year-by-year heatmaps of log-index covariance and correlation from the lobster glmmTMB model.

Annual log-index covariance (left) and correlation (right) for the same standardised lobster index. Each cell relates a pair of annual estimates; diagonal cells describe each year itself. The matrices retain common fitted-parameter uncertainty and are not residual autocorrelation estimates. Indices are unscaled, with the same reference profile and zero-month-effect convention as the preceding table.

A possible assessment likelihood relates this vector to expected abundance through a catchability parameter:

log⁡𝐈̂∼̇MVN(logq+log𝐁,𝚺CPUE+𝚺extra). \log \widehat{\boldsymbol I} \mathrel{\dot\sim} \mathrm{MVN}\!\left(\log q + \log \boldsymbol B,\, \boldsymbol\Sigma_{\mathrm{CPUE}} + \boldsymbol\Sigma_{\mathrm{extra}}\right).

This is a modelling choice and an approximation, not an assessment fitted by influ2. Replacing the full covariance by its diagonal assumes independent annual estimation errors. Keeping the off-diagonal entries preserves their estimated dependence, including uncertainty in year-to-year contrasts. It can increase or decrease uncertainty in particular contrasts; it does not guarantee narrower intervals, a changed stock-status estimate, or removal of bias. Hoyle et al. (2024) emphasise covariance propagation when aggregating predictions (Section 5.8) and distinguish index estimation error from additional catchability/process variation (Section 5.5). The matrix supplies the former; the assessment must justify any additional error model separately.

Use the default rescale = "raw" for this workflow when the assessment estimates catchability. Dividing every uncertain annual series by its own geometric mean removes a common log-level and makes the full log covariance singular. require_pd = TRUE catches this; influ2 does not add jitter to make it invertible. A contrast-based likelihood is another deliberate assessment design, not something to obtain by silently dropping a year. A single known unit conversion, in contrast, leaves log covariance unchanged.

Keep the table and both axes of the matrix in exactly the same year order. The following bundle is ready to save with saveRDS() and inspect in the assessment project; it retains neither the CPUE model nor its posterior draws.

assessment_input <- list(
  table = assessment_table,
  log_covariance = Sigma_log,
  response_covariance = Sigma_response,
  definition = lobster_cpue$metadata
)

For a subset, use index_vcov(lobster_cpue, years = selected_years) and match the table to those same labels. Separate index objects do not imply zero cross-series covariance: combining regions or reporting systems still needs an explicit dependence assumption. For brms, this is posterior covariance, not a prior-free likelihood; a two-stage assessment must consider the first model’s priors rather than count that information twice.

Marginal lognormal table parameters

For an assessment that instead requires one lognormal distribution per year, index_table(..., format = "lognormal") adds Meanlog, SDlog, and LognormalMedian, calculated from Mean and SD. These are explicitly moment-matched approximations to index uncertainty, not the distribution of the observed response and not replacements for genuine posterior medians.

lognormal_table <- index_table(lobster_cpue, format = "lognormal")
knitr::kable(
  lognormal_table[c("Year", "Meanlog", "SDlog", "LognormalMedian")],
  digits = 3,
  caption = "Marginal lognormal approximations to the standardised index uncertainty. These are an alternative assessment-table convention, not the joint matrix's log-scale parameters."
)
Marginal lognormal approximations to the standardised index uncertainty. These are an alternative assessment-table convention, not the joint matrix’s log-scale parameters.
Year Meanlog SDlog LognormalMedian
2000 0.514 0.141 1.672
2001 0.556 0.141 1.744
2002 0.601 0.139 1.824
2003 0.328 0.136 1.388
2004 0.452 0.135 1.572
2005 0.266 0.141 1.305
2006 0.145 0.135 1.156
2007 0.167 0.135 1.181
2008 0.158 0.139 1.172
2009 0.116 0.139 1.123
2010 0.197 0.136 1.218
2011 0.320 0.136 1.377
2012 0.186 0.139 1.205
2013 0.305 0.135 1.357
2014 0.220 0.135 1.246
2015 0.048 0.136 1.049
2016 0.077 0.134 1.081
2017 0.098 0.136 1.103

In general, Meanlog differs from log(Mean), and SDlog differs from sqrt(diag(Sigma_log)). Do not combine these marginal SDs with the matrix’s correlations without explicitly choosing a different uncertainty model. The CPUE indices article gives the formulae, calculation sources, and safeguards.

One interface and compact uncertainty

All fitted-model methods return an influ_diag:

methods("influ")
#> [1] influ.brmsfit*  influ.default*  influ.gam*      influ.glm*     
#> [5] influ.glmmTMB*  influ.sdmTMB*   influ.tinyVAST*
#> see '?methods' for accessing help and source code

The result contains compact tables for influence, coefficient or field summaries, data composition, and index comparisons:

head(influ_effects(glm_diagnostic))
#>   focus level term   component scale   estimate  std_error       lower
#> 1  year  2000 year conditional  link 0.26025542 0.05632333  0.14986373
#> 2  year  2001 year conditional  link 0.31968907 0.05442756  0.21301301
#> 3  year  2002 year conditional  link 0.35088853 0.05020332  0.25249183
#> 4  year  2003 year conditional  link 0.06192851 0.04888310 -0.03388060
#> 5  year  2004 year conditional  link 0.23677462 0.04454537  0.14946731
#> 6  year  2005 year conditional  link 0.02161548 0.05467925 -0.08555389
#>       upper              method
#> 1 0.3706471 analytic covariance
#> 2 0.4263651 analytic covariance
#> 3 0.4492852 analytic covariance
#> 4 0.1577376 analytic covariance
#> 5 0.3240819 analytic covariance
#> 6 0.1287849 analytic covariance
head(influ_composition(glm_diagnostic))
#>   focus level term term_level   n weight      effect proportion   component
#> 1  year  2000 year       2000 229    229  0.00000000          1 conditional
#> 2  year  2001 year       2001 227    227  0.05943365          1 conditional
#> 3  year  2002 year       2002 249    249  0.09063311          1 conditional
#> 4  year  2003 year       2003 307    307 -0.19832691          1 conditional
#> 5  year  2004 year       2004 266    266 -0.02348080          1 conditional
#> 6  year  2005 year       2005 213    213 -0.23863994          1 conditional
influ_indices(glm_diagnostic)
#>    focus level       series  estimate  std_error     lower     upper    scale
#> 1   year  2000      nominal 1.4803493 0.11046880 1.2638345 1.6968642 response
#> 2   year  2001      nominal 1.6343612 0.12755150 1.3843649 1.8843576 response
#> 3   year  2002      nominal 1.7469880 0.13605329 1.4803284 2.0136475 response
#> 4   year  2003      nominal 1.4169381 0.09985150 1.2212328 1.6126434 response
#> 5   year  2004      nominal 1.9849624 0.15187577 1.6872914 2.2826334 response
#> 6   year  2005      nominal 1.5586854 0.13659395 1.2909662 1.8264047 response
#> 7   year  2006      nominal 1.5304054 0.11867176 1.2978130 1.7629978 response
#> 8   year  2007      nominal 1.5173502 0.10253518 1.3163849 1.7183154 response
#> 9   year  2008      nominal 1.3201320 0.09906052 1.1259770 1.5142871 response
#> 10  year  2009      nominal 1.2638436 0.09420120 1.0792127 1.4484746 response
#> 11  year  2010      nominal 1.5269841 0.11822674 1.2952640 1.7587043 response
#> 12  year  2011      nominal 1.9715302 0.14321514 1.6908337 2.2522268 response
#> 13  year  2012      nominal 1.9039301 0.14955989 1.6107981 2.1970621 response
#> 14  year  2013      nominal 2.7320755 0.18020926 2.3788718 3.0852791 response
#> 15  year  2014      nominal 2.6860841 0.16650531 2.3597397 3.0124285 response
#> 16  year  2015      nominal 2.2684564 0.14807205 1.9782405 2.5586723 response
#> 17  year  2016      nominal 2.3801170 0.13585946 2.1138373 2.6463966 response
#> 18  year  2017      nominal 2.3750000 0.15535100 2.0705176 2.6794824 response
#> 19  year  2000 standardised 1.2972614 0.07321457 1.1616759 1.4486718    ratio
#> 20  year  2001 standardised 1.3766996 0.07507260 1.2374008 1.5316799    ratio
#> 21  year  2002 standardised 1.4203290 0.07142035 1.2872290 1.5671916    ratio
#> 22  year  2003 standardised 1.0638863 0.05208566 0.9666869 1.1708590    ratio
#> 23  year  2004 standardised 1.2671555 0.05651764 1.1612155 1.3827606    ratio
#> 24  year  2005 standardised 1.0218508 0.05598106 0.9180037 1.1374454    ratio
#> 25  year  2006 standardised 0.9090193 0.04181697 0.8307469 0.9946664    ratio
#> 26  year  2007 standardised 0.9158739 0.04154178 0.8380655 1.0009063    ratio
#> 27  year  2008 standardised 0.8965231 0.04513042 0.8124222 0.9893301    ratio
#> 28  year  2009 standardised 0.8538263 0.04372493 0.7724171 0.9438157    ratio
#> 29  year  2010 standardised 0.9617778 0.04421246 0.8790188 1.0523285    ratio
#> 30  year  2011 standardised 1.0709101 0.04567821 0.9851182 1.1641735    ratio
#> 31  year  2012 standardised 0.9481975 0.04563182 0.8629695 1.0418429    ratio
#> 32  year  2013 standardised 1.0530734 0.04026025 0.9771170 1.1349343    ratio
#> 33  year  2014 standardised 0.9798923 0.03585484 0.9121348 1.0526832    ratio
#> 34  year  2015 standardised 0.8071257 0.03230505 0.7462887 0.8729221    ratio
#> 35  year  2016 standardised 0.8310860 0.03062793 0.7732215 0.8932808    ratio
#> 36  year  2017 standardised 0.8632611 0.03393965 0.7992997 0.9323407    ratio
#>      component
#> 1  conditional
#> 2  conditional
#> 3  conditional
#> 4  conditional
#> 5  conditional
#> 6  conditional
#> 7  conditional
#> 8  conditional
#> 9  conditional
#> 10 conditional
#> 11 conditional
#> 12 conditional
#> 13 conditional
#> 14 conditional
#> 15 conditional
#> 16 conditional
#> 17 conditional
#> 18 conditional
#> 19 conditional
#> 20 conditional
#> 21 conditional
#> 22 conditional
#> 23 conditional
#> 24 conditional
#> 25 conditional
#> 26 conditional
#> 27 conditional
#> 28 conditional
#> 29 conditional
#> 30 conditional
#> 31 conditional
#> 32 conditional
#> 33 conditional
#> 34 conditional
#> 35 conditional
#> 36 conditional
influ_metrics(glm_diagnostic)
#>             term   component  metric     estimate std_error lower upper
#> 1           year conditional overall  0.147765774        NA    NA    NA
#> 2           year conditional   trend -0.024481968        NA    NA    NA
#> 3          month conditional overall  0.200153750        NA    NA    NA
#> 4          month conditional   trend  0.037814471        NA    NA    NA
#> 5 poly(depth, 3) conditional overall  0.116432819        NA    NA    NA
#> 6 poly(depth, 3) conditional   trend -0.004176475        NA    NA    NA
#> 7  poly(soak, 3) conditional overall  0.140917166        NA    NA    NA
#> 8  poly(soak, 3) conditional   trend  0.024185229        NA    NA    NA
#>                method
#> 1 analytic covariance
#> 2 analytic covariance
#> 3 analytic covariance
#> 4 analytic covariance
#> 5 analytic covariance
#> 6 analytic covariance
#> 7 analytic covariance
#> 8 analytic covariance

Calculation and retention are separate choices:

# Calculate uncertainty, retaining summaries only.
d1 <- influ(fitted_model, focus = "year", retain = "summary")

# Retain exact draws only for the derived diagnostics.
d2 <- influ(fitted_model, focus = "year", retain = "derived_draws")

# Write compact diagnostic draws to disk.
d3 <- influ(
  fitted_model,
  focus = "year",
  retain = "disk",
  draws_path = "influence-draws.rds"
)

# Fast preview from coefficient estimates or posterior means.
d4 <- influ(fitted_model, focus = "year", uncertainty = "none")

For GLMs and GAMs, linear diagnostic contrasts use fitted joint covariance analytically. Joint coefficient simulation is used when derived draws are requested. The brms backend reduces joint posterior draws during calculation, glmmTMB jointly simulates fixed components for hurdle and zero-inflated means, and the spatial backends reduce sparse joint-precision draws to the same compact schema. This separation draws on the posterior-processing approach used in CPUETools (Dragonfly Science, n.d.), while the S3 design and GAM implementation were also informed by gamInflu (Dunn 2025).

Save today, reopen tomorrow

Save the calculated result objects to reuse their tables and plots in a later R session. They retain the estimates, interval summaries, and diagnostic metadata without retaining the fitted models by default. The same workflow applies to influence diagnostics, residual diagnostics, CPUE indices, and step comparisons.

Here we reuse the influence, index, and step results calculated above. Only the residual diagnostic is calculated for the first time; it uses 100 response simulations from the existing glmmTMB fit, not a model refit or MCMC run. This example requires glmmTMB and MASS, as did the preceding index and step examples.

lobster_results <- list(
  influence = glmmTMB_diagnostic,
  residuals = influ_residuals(lobster_glmmTMB, nsim = 100, seed = 281),
  index = lobster_cpue,
  steps = lobster_steps
)

# The vignette uses a temporary file; choose a persistent path in your analysis.
# For example: result_file <- "lobster-results.rds"
result_file <- tempfile(fileext = ".rds")
saveRDS(lobster_results, result_file)

In the later session, load influ2 and read that file. The vignette reads it back immediately; the package tests additionally check this workflow in a separate, clean R session with no original fitted models. Reopening and plotting the summaries do not repeat the response simulations, CPUE prediction calculation, or step-model refits.

library(influ2)
# After restarting R, set result_file to the persistent path used above:
# result_file <- "lobster-results.rds"
restored_results <- readRDS(result_file)

head(index_table(restored_results$index))
#>   Year     Mean        SD        CV    Qlower   Qupper       Method
#> 1 2000 1.688662 0.2399914 0.1421192 1.2781173 2.231079 standardised
#> 2 2001 1.761248 0.2494645 0.1416407 1.3343068 2.324799 standardised
#> 3 2002 1.841306 0.2566028 0.1393591 1.4012104 2.419629 standardised
#> 4 2003 1.400957 0.1908672 0.1362406 1.0726465 1.829755 standardised
#> 5 2004 1.585962 0.2156039 0.1359452 1.2149997 2.070187 standardised
#> 6 2005 1.317816 0.1864175 0.1414594 0.9987212 1.738863 standardised
#>   Distribution Link
#> 1      nbinom2  log
#> 2      nbinom2  log
#> 3      nbinom2  log
#> 4      nbinom2  log
#> 5      nbinom2  log
#> 6      nbinom2  log

# Recreate the plots without repeating the calculations.
restored_plots <- list(
  influence = plot(restored_results$influence),
  residuals = plot(restored_results$residuals),
  index = plot_index(restored_results$index),
  steps = plot_step(restored_results$steps)
)

Print an element, such as restored_plots$residuals, to display it. Saving the result objects, rather than only their figures, allows later changes to labels and layout using the stored results. Keep a record of the R and package versions, for example with sessionInfo(): these tests check reuse with the same package version, not indefinite compatibility with future releases.

This compact save does not replace the original fits and analysis code when new predictions, simulations, or refits are needed. It also does not package separate draw files created with retain = "disk"; keep those files separately if they are needed. The restart checks cover the default summary-only objects, not optional retained fitted models or external draw-file relocation. Retained residual objects include observation-level responses and diagnostic summaries, so check data-sharing permissions before sending them to someone else.

Families and response structures

The initial family set is:

influ_families()
#>              family                       aliases default_scale
#> 1          gaussian                        normal    difference
#> 2          binomial                     bernoulli    difference
#> 3           poisson                       poisson         ratio
#> 4 negative_binomial negbinomial, nbinom1, nbinom2         ratio
#> 5         lognormal                     lognormal         ratio
#> 6             gamma                         Gamma         ratio
#> 7           tweedie                       Tweedie         ratio

It covers Gaussian, binomial, Poisson, negative binomial, lognormal, Gamma, and Tweedie responses. Hurdle/delta combinations cover Gamma, lognormal, Poisson, and negative-binomial positive components. Zero-inflated combinations cover Poisson and negative-binomial counts. Quasi families and more specialised positive distributions are deliberately outside the initial scope.

Model-structure boundaries

A standardised year-effect index requires an unambiguous term depending only on year. A term such as year:area, or several terms involving year, does not define one index without an additional marginalisation choice. influ() retains those influence terms but warns and omits the standardised index. Supplying reference_data changes the reference distribution; it does not automatically perform that marginalisation. Step plots need an unambiguous focus effect as well. The grouped generalised residual diagnostic does not add residuals to a coefficient and therefore needs no such baseline.

Fixed offsets are retained when models are refitted. Single-component log-link ratios and identity-link contrasts can be calculated with offsets held unchanged, because the reference offset cancels. An offset is not an estimated term and is not assigned its own influence coefficient. Nonlinear probability and combined hurdle/zero-inflated diagnostics with offsets are explicitly unsupported for now. For catch models with an effort offset, nominal summaries still report mean observed catch, not catch divided by effort; supply an appropriate separate nominal CPUE series if needed.

brms lognormal models require constant sigma and their usual identity location link. Varying log-scale models are rejected rather than treating a log-location ratio as an arithmetic-mean ratio. In contrast, glmmTMB parameterises the lognormal arithmetic mean directly: its log-link mean ratios remain valid with varying dispersion, although dispersion effects are not separately decomposed. Mean-parameterised lognormal backends require a log link for these ratio diagnostics. These boundaries do not add area weighting or spatially integrated abundance to the year-effect indices.

The separate hurdle and zero-inflation vignette explains named components and unconditional means. The Bentley validation vignette runs the frozen original proto implementation, compares values, and displays old and new plots side by side. The spatial and spatiotemporal vignette maps persistent and time-varying fields alongside their influence components.

References

Anderson, Sean C., Eric J. Ward, Phil A. English, Lewis A. K. Barnett, and James T. Thorson. 2025. “sdmTMB: An r Package for Fast, Flexible, and User-Friendly Generalized Linear Mixed Effects Models with Spatial and Spatiotemporal Random Fields.” Journal of Statistical Software 115 (2): 1–46. https://doi.org/10.18637/jss.v115.i02.
Bentley, Nokome, Terese H. Kendrick, Paul J. Starr, and Paul A. Breen. 2012. “Influence Plots and Metrics: Tools for Better Understanding Fisheries Catch-Per-Unit-Effort Standardizations.” ICES Journal of Marine Science 69 (1): 84–88. https://doi.org/10.1093/icesjms/fsr174.
Brooks, Mollie E., Kasper Kristensen, Koen J. van Benthem, et al. 2017. “glmmTMB Balances Speed and Flexibility Among Packages for Zero-Inflated Generalized Linear Mixed Modeling.” The R Journal 9 (2): 378–400. https://doi.org/10.32614/RJ-2017-066.
Bürkner, Paul-Christian. 2017. “Brms: An r Package for Bayesian Multilevel Models Using Stan.” Journal of Statistical Software 80 (1): 1–28. https://doi.org/10.18637/jss.v080.i01.
Dragonfly Science. n.d. CPUETools. https://github.com/dragonfly-science/CPUETools.
Dunn, Alistair. 2025. gamInflu: Influence Analysis for Generalized Additive Models. https://github.com/alistairdunn1/gamInflu.
Hoyle, Simon D., Robert A. Campbell, Nicholas D. Ducharme-Barth, et al. 2024. “Catch Per Unit Effort Modelling for Stock Assessment: A Summary of Good Practices.” Fisheries Research 269: 106860. https://doi.org/10.1016/j.fishres.2023.106860.
Hsu, Jhen, Yi-Jay Chang, and Nicholas D. Ducharme-Barth. 2022. “Evaluation of the Influence of Spatial Treatments on Catch-Per-Unit-Effort Standardization: A Fishery Application and Simulation Study of Pacific Saury in the Northwestern Pacific Ocean.” Fisheries Research 255: 106440. https://doi.org/10.1016/j.fishres.2022.106440.
Thorson, James T., Sean C. Anderson, Pamela Goddard, and Christopher N. Rooper. 2025. “tinyVAST: R Package with an Expressive Interface to Specify Lagged and Simultaneous Effects in Multivariate Spatio-Temporal Models.” Global Ecology and Biogeography 34 (4): e70035. https://doi.org/10.1111/geb.70035.
Wood, Simon N. 2017. Generalized Additive Models: An Introduction with r. 2nd ed. Chapman; Hall/CRC. https://doi.org/10.1201/9781315370279.