Changelog
Source:NEWS.md
sdmTMBexperiments 0.1.9
2026-11-04 Influence screens, data-filter scenarios, regional index split, forked parallel workers and automatic nsplit.
New features
-
density_share()is a model-free screen of each haul’s (or cruise’s) share of the year’s summed density, with its share of the year’s hauls and an optional candidate screen (top_n,min_share) forsdmTMB_leave_out(). -
sdmTMB_leave_out()returnshaul_shareandleverage(influence / haul_share) per identifier, andplot(type = "leverage")labels identifiers withinfluence >= 0.1andleverage >= 2. Raw influence mostly flags the main survey of a year; leverage separates identifiers that move the index far more than their sample size warrants. - New
sdmTMB_data_scenarios()(+plot()) refits a model under named data filters (rows dropped or values modified) or alternative prediction grids, on the same mesh with warm starts, to compare general filtering rules with named exclusions. - New
peak_ratio()measures an index value against its neighbouring years, for scenario, leave-out or plain index tables. - New
index_by_region()(+plot()) splits the plug-in index by a region column of the grid, with each region’s share and its ratio to its own mean.
Changes
-
n_workers > 1now uses forkedfuture::multicoreworkers where supported (macOS/Linux outside RStudio) andmultisessionotherwise. Forked workers survive an SSH logout in a detachedscreen/tmuxsession;multisessionworkers can be killed by systemd. -
nsplitdefaults toNULLeverywhere: about 75 000 grid rows per split. Bias-corrected extraction on chunks of several hundred thousand rows can crash the R session without an R error. -
compare_surveys()warns whencompiled_indexand the survey indices differ by more than 1000 times, the symptom of a compiled index passed in other units (e.g. kt instead of kg).
sdmTMBexperiments 0.1.8
2026-11-03 Survey-specific settings, the same-area comparison and leave-one-out refits for survey comparisons.
New features
-
survey_spec()defines a survey forcompare_surveys(): one or several series combined, and its own formula, family, random fields,extra_time, mesh cutoff, data filter, depth band, hull settings, prediction grid and prediction values (e.g.list(cruiseseries = "A")for a model with the series as a fixed effect). -
compare_surveys()accepts a named list of series vectors orsurvey_spec()objects and limits survey grids to a depth band (depth_range, or"data"for haul-depth quantiles). With acompiled_modelit also computes a same-area comparison: the survey and compiled models predicted over the same cells (domain = "shared"in$index,plot(type = "same_area")). It returns a$settingstable and reports survey indices only for years with data. - New
sdmTMB_leave_out()(+plot()) refits a model without each survey series, cruise or haul and reports the change in the index. Refits keep every setting of the original call; years without data after a removal are kept asextra_timeand reported asNA. -
make_prediction_grid()gainsmin_depth.
sdmTMBexperiments 0.1.7
- Pin the older-R Linux check to Ubuntu 24.04 to avoid the rolling ubuntu-latest image’s system-package download delays.
sdmTMBexperiments 0.1.6
- Publish pkgdown directly through GitHub Pages artifacts, with source-version verification and no generated commits.
sdmTMBexperiments 0.1.5
2026-10-16 Merge the refit, xy_cols and dependency fixes with the model-selection functions.
New features
- New
sebastes_bathydataset (a ~45 KB coarse ETOPO 2022 depth raster of thesebastesstudy area) andsebastes_depth(), adepth_funformake_prediction_grid(). The examples, vignette and tests use them so they no longer need the ~85 MB bathymetry downloadggOceanMaps::get_depth()makes on first use. For demonstration only.
Bug fixes
Map helpers no longer fail with
object 'dd_land' not foundwhen ggOceanMaps is loaded but not attached (as in vignettes, tests and CI); ggOceanMaps is now attached for the duration of the call. The depth download inmake_prediction_grid()also uses a longer timeout (600 s).sdmTMB_retro(),compare_meshes(),sdmTMB_mesh_cv()andcompare_surveys()(withcompiled_model) now refit models from the originalsdmTMB()call, assdmTMB::update.sdmTMB()does, instead of rebuilding the call from a handful of fields. Previously settings such asspatial_varying,time_varying,extra_time,weights,priors,share_range,anisotropyandremlwere silently dropped, andsdmTMB_mesh_cv()also dropped the offset. The call’s arguments are evaluated on the main process so they also reach parallel workers.compare_surveys()errors on a numericoffset/weightsvector it cannot subset to the survey data.Coordinate columns are no longer assumed to be
X/Y: fitted models useobject$spde$xy_cols, andcompare_surveys()gains anxy_colsargument forcompiled_model = NULL.compare_surveys()no longer returns a garbledsanitytable when one survey model fails to fit; failed fits get a column ofNAs with the same checks as the others.plot.sdmTMB_model_comparison()now always draws the reference model in black, whatever the model names orreference_model. Index plots no longer giveNAcolours with more than seven series.Non-integer mesh cutoffs (e.g.
7.5) are no longer truncated or turned intoNAbycompare_meshes()andsdmTMB_mesh_cv().compare_meshes()no longer errors when the reference refit fails; it warns and returnsNAprediction differences.sdmTMB_retro()andcompare_surveys()now accept a numericareavector with one value per row ofnewdata, subsetting it along with the prediction grid.Models with a time column not named
yearnow work: index outputs always name their time columnyear, so plots,mohns_rho()and failed-fit rows line up.compare_surveys(),compare_models()andcompare_meshes()gain acoord_multiplierargument (default1000, km to m), used for survey footprint clipping and the spatial maps, which previously assumed km.sdmTMB_mesh_cv()now takes its defaultxy_colsfrom the model mesh and errors clearly on theNACRS thatmake_mesh()produces.sdmTMB_retro(refit_meshes = FALSE)now keeps a barrier mesh’s barrier.compare_meshes(),compare_surveys(),sdmTMB_mesh_cv()andsdmTMB_retro(refit_meshes = TRUE)warn that the meshes they build have no barrier.plot_variance_partitioning()now only uses components whose SD is a link-scale SD:sigma_O,sigma_E, random intercepts, andphionly forgaussian()(identity link) andlognormal(). Previously it addedphifor any family (a dispersion or shape parameter for e.g. Tweedie, Gamma and NB2) and smooth-term SDs, which are not link-scale variances.The index-plot annotation of
plot.sdmTMB_model_comparison()andplot.sdmTMB_mesh_comparison()(mohn = TRUE) is now labelled “Terminal-year rel. diff.”: all series there end in the same year, so it is not a retrospective Mohn’s rho.plot_prediction_histogram()divides the observed response byexp(offset)for models with an offset, so it is on the same scale as the offset-free grid predictions.sdmTMB_retro()’spredict_argsis now passed on tosdmTMB::get_index_split(); it previously had no effect.plot.sdmTMB_retro()keeps peels in order with more than ten peels.plot.sdmTMB_survey_comparison(type = "scatter")no longer errors for a survey with a flat index, and no longer warns about.datain tidyselect.The deprecated
transargument of ggplot2 scales is replaced withtransform; ggplot2 >= 3.5.0 is now required.Failed fits now report the actual error instead of “did not converge”, and fits that ran but fail
sdmTMB::sanity()are flagged.
Breaking changes
-
sdmTMB_retro(refit_meshes = TRUE)now requires acutoffargument instead of approximating it from the minimum mesh vertex distance. -
sdmTMB_retro()’sn_workersdefaults to1(wasnyears). Withn_workers = 1all functions now use the user’s currentfuture::plan()instead of forcing a sequential one, andfuture.globals.maxSizeis restored on exit. - The
%>%re-export is removed.
Other changes
-
ggOceanMapsmoves from Depends to Suggests. Spatial plots fall back to a plain map without it;bubble_map()and automatic depth extraction inmake_prediction_grid()require it. -
plyr,magrittrandviridisare no longer dependencies. -
sdmTMB_mesh_cv()documents AIC across meshes as a rough guide only. -
tests/andvignettes/are no longer build-ignored, soR CMD checkon CI now runs the tests and builds the vignette. New tests check that refits reproduce the original fit.
sdmTMBexperiments 0.1.4
2026-10-25 Restrict pkgdown write permission to the deployment job; pull requests build the site without deploying.
sdmTMBexperiments 0.1.3
2026-10-14 Regenerate plot_mesh.Rd so its usage lists the new plot_mesh() arguments (fixes the R CMD check codoc WARNING).
sdmTMBexperiments 0.1.2
2026-10-14 Adds the model-selection functions and fixes delta-family labelling in sdmTMB_selection().
Model selection
A set of functions for building defensible model-selection tables, ported from two survey- and CPUE-index workflows:
-
sdmTMB_selection()fits a named list of candidate specifications to identical records and returns a tidy table: the full specification of each candidate, its crash/convergence/sanity status, in-sample NLL and AIC with delta-AIC and Akaike weights, and — withcv— cross-validated predictive skill. One function covers component ranking, dropping fixed effects, family selection, spatiotemporal-structure selection and mesh resolution, since each is the same operation with a different thing varied. Caches one file per candidate (resumable, andonlylets a long ladder be spread over several R sessions). -
selection_frame()builds the complete-case frame that makes those columns comparable in the first place: candidates that use different covariates otherwise get silently fitted to different subsets of the data. -
sdmTMB_cv_summary()summarises ansdmTMB::sdmTMB_cv()run per held-out observation rather than summed over folds, and reportsfolds_pdhessandn_heldoutso a fragile row is visible as such. A summed hold-out log-likelihood rewards whichever candidate scored fewest points. -
best_candidate()looks the winner up in the metric column rather than by row position, skipping rows the table marks as not comparable. Also builds a cross-validation shortlist from an AIC ladder (n). -
add_aic_weights()addsdeltaAIC/AICweightto any table with anAICcolumn, with agroupargument for tables that mix models fitted to different response data (where a common weight would be meaningless). -
plot()methods:type = "selection"is a status-coded dot plot of the ranking;type = "index"overlays the survey index each candidate produces, which answers the question a selection table cannot — whether the choice actually moves the index.
Plotting
-
plot_mesh()gainsland_col,lon_interval/lat_interval,utm_axesandzero_col/zero_size/zero_alpha. All default to the previous behaviour, so existing figures are unchanged.-
utm_axes = TRUElabels the axes in projected kilometres — the units the model actually fits in — instead of degrees. The breaks are explicit numeric values derived from the mesh extent; a breaks function is evaluated against the panel range rather than thecoord_sf()graticule and bunches every label into one corner. -
zero_coldraws stations whosevalueis zero as small faint crosses beneath the sized points. Without it they are invisible, becausescale_size_area()maps zero to zero area — yet in a survey dataset the zeros are usually the majority of the stations and carry the absence information. They are drawn small and faint by default for the same reason.
-
Fixes
-
.safe_index()(used bycompare_models(),compare_meshes(),compare_surveys(),sdmTMB_retro()and the new selection index plot) now survives a failed index extraction, not only a failed fit.get_index_split()can throw for a model that fitted perfectly well — anewdatagrid that does not overlap the fitted coordinates, a year in the grid but not in the model, a split landing on an empty subset — and because these functions summarise many models in one figure at the end of a multi-hour render, one unlucky model used to take the whole plot with it. It is now warned about (naming the model where the caller supplies a label) and returned as atype = "crashed"row. -
sdmTMB_selection()handles delta families: the table has exactly one row per candidate (a delta family’s two-elementfamily$familyno longer recycles the row), and thefamilycolumn andspecstring use sdmTMB’sclean_name(e.g.delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link')) instead of the first component’s family, which labelled a Poisson-link delta fit as “binomial”. Non-delta families are unchanged (e.g. “tweedie”).
sdmTMBexperiments 0.1.0
First release. Experimental helper functions for fitted sdmTMB models, ported and generalised from project-local helpers.
Functions
- Model comparison:
compare_models()(+plot()method). - Mesh-resolution sensitivity:
compare_meshes()(+plot()method). - Retrospective analysis:
sdmTMB_retro()(+plot()method). - Survey-specific index comparison:
compare_surveys()(+plot()method: index, scatter, spatial). - Spatial mesh cross-validation:
sdmTMB_mesh_cv()(+plot()method). - Prediction grids:
make_survey_prediction_region(),make_prediction_grid(). -
mohns_rho()— Mohn’s rho for any set of index series. - Plotting helpers:
plot_mesh(),plot_prediction_map(),plot_random_field(),plot_index(),plot_fixed_effects(),plot_variance_partitioning(),plot_prediction_histogram(),plot_residual_qq(), andbubble_map(). - Bundled public example data:
sebastes.
Fixes applied during extraction
- Assign
future.globals.maxSizein the low-memory branch (previously computed but never applied). - Correct the per-cell
mean_pr_diffpercent-difference formula incompare_models()andcompare_surveys(). - Unify the parallel seed argument to
future_seedeverywhere. - Validate
X/Ycolumns incompare_meshes(). - Palette-safe colours in
plot.sdmTMB_mesh_comparison()for any number of cutoffs. -
sdmTMB_retro()rebuilds the mesh’s data mapping for each peel whenrefit_meshes = FALSE(previously reusing the fittedsdmTMBmeshunchanged meant every peel but the terminal one failed to converge and silently returnedNAindex rows), and clampsnyearsto the available years. - Use portable
future::multisessionparallelism (works on Windows). -
compare_surveys()no longer silently inherits anNACRS fromcompiled_modelwhenobject_crsis leftNULL(a normally builtsdmTMB::make_mesh()mesh hascrs = NA, notNULL); it now errors with a clear message asking forobject_crsinstead of failing deep insideplot(..., type = "spatial"). -
plot_prediction_map()no longer hardcodes alimits = c(0, NA)fill scale, which silently rendered any out-of-range cell (e.g. negative link-scale predictions) as blank/grey with no indication why. The scale range is now taken from the data by default; a newfill_limitsargument (defaultNULL) lets you opt into an explicit floor/ceiling, and a warning fires only if that explicit choice actually clips data. -
sdmTMB_mesh_cv()’sdeltaNLL/deltaNLL_prhad the wrong sign: they were computed straight fromsum_loglik(a log-likelihood, where higher is better) without negating it, so despite the name they were a delta-log-likelihood (<= 0, max = best) rather than a proper delta-NLL. Now>= 0with0(min) indicating the best-fitting mesh cutoff, consistent withdeltaAIC. - The vignette’s
compare_surveys()example fitdensityas the response while also passing aswept_arealog-offset; sincedensityis alreadybiomass / swept_area, this double-corrected for effort and, combined with anareaargument left in km² instead ofswept_area’s square nautical miles, inflated the resulting index by roughly 1000x. Fixed by fittingbiomassas the response and convertingareato square nautical miles. -
.spatial_diff_map()’s (the shared spatial-difference map used by all three comparisonplot()methods) fill-scale breaks could be crowded/ illegible for skewed or extreme ranges, and didn’t guarantee the scale’s actual limits or0were shown. Breaks are now guaranteed to include the limits and0(when spanned), de-crowded so adjacent breaks are never too close together, and labelled with abbreviated numbers (e.g."-9.2K") viascales::label_number(scale_cut = scales::cut_short_scale()). -
compare_models()/compare_surveys()’s spatialplot()methods unconditionally labelled the fill legend “Percent anomaly” even whenspatial_response = "mean"(an absolute density difference, not a percent);compare_meshes()didn’t have this bug. Fixed by only applying the percent label whenspatial_responseis actually one of the percent-based options. - The rightmost year’s x-axis label was cropped in every index plot (
plot_index(), and thetype = "index"plot()methods of the three comparison functions andsdmTMB_retro()):coord_cartesian(expand = FALSE)puts the last data point exactly on the panel edge, and the default plot margin didn’t leave room for its tick label. Fixed with a shared, wider plot margin. - Those same index plots’ x-axis could show decimal year breaks (
2020.0,2020.5, …) for short year ranges, sincescales::pretty_breaks()picks sub-1 steps when that gives a better fit to the target break count. Whole year ranges now always get whole-year breaks (falling back to the regular algorithm only if the range itself isn’t whole numbers). -
R CMD checkand CI (R-CMD-check.yaml,pkgdown.yaml) failed entirely at dependency resolution:sdmTMBextrawas listed inSuggestsbut is a GitHub-only package (not on CRAN/Bioconductor), whichpakcan’t resolve. Removed it fromSuggests(it was only referenced in one roxygen doc link, never used in code). Also fixed, found while investigating: a non-ASCII character in asprintf()string incompare-surveys.R, an undocumented...argument inplot.sdmTMB_prediction_grid(), and a strayRplots.pdfaccidentally committed at the repo top level.
Other changes
-
plot.sdmTMB_model_comparison(type = "index")gains amohnargument (defaultFALSE) controlling whether the Mohn’s rho annotation is added; previously it was always shown. -
plot.sdmTMB_model_comparison(type = "spatial")gains afill_prefixargument for the fill legend title (e.g."Biomass density","CPUE"). Previously the legend was hardcoded to “Biomass densityanomaly”, which doesn’t hold for models with other response variables. DefaultNULLnow gives a generic “Percent anomaly” title. -
plot_prediction_map()’stransargument is renamedtransform(default"identity", was"sqrt"), matching ggplot2’scontinuous_scale()API (transis deprecated there in favour oftransform). -
plot.sdmTMB_mesh_comparison(type = "index")gains the samemohnargument (defaultFALSE) asplot.sdmTMB_model_comparison(), and acutoff_labelargument (default"Cutoff") for the colour/fill legend title, e.g.cutoff_label = "Cutoff (km)". -
compare_meshes()now also computesmean_pr_diff, sospatial_response = "mean_pr_diff"is available forplot.sdmTMB_mesh_comparison(type = "spatial")— previously onlycompare_models()/compare_surveys()computed it, so mesh comparisons silently had one fewer (and arguably the most useful) spatial-plot option. - Documented what the
spatial_responseoptions ("mean","pr_mean","mean_pr_diff") actually compute and when to prefer each, in theplot()methods of all three comparison functions (?plot.sdmTMB_model_comparisonetc.) and in the vignette. -
make_prediction_grid()output now carries classsdmTMB_prediction_gridandcrs/coord_multiplier/resolutionattributes, and gains aplot.sdmTMB_prediction_grid()method: draws the grid points as squares on a [ggOceanMaps::basemap()], optionally coloured by a column (depthby default, when present). -
make_prediction_grid()now extracts depth automatically whenmax_depthis set and nodepth_funis supplied, viaggOceanMaps::get_depth(bathy.style = bathy_style)(newbathy_styleargument, default"raster_continuous", ggOceanMaps’ bundled ETOPO grid — no extra download path needed). A customdepth_funstill overrides this for other bathymetry sources. -
compare_models(),compare_meshes()andcompare_surveys()no longer leaksdmTMB’s informational “assuming the offset vector is 0” message (frompredict.sdmTMB()) when comparing models fit with an offset. This is expected/correct — prediction grids aren’t real hauls, so the offset is deliberately zero and area-weighting is done via theareaargument instead — but it was showing on every spatial-difference calculation (.pred_diff_base(), shared by all three comparison functions) with no way to turn it off;.safe_index()’sget_index_split()call already suppressed it. - Default
spatial_responsefor all three comparisonplot()methods changes from"pr_mean"to"mean_pr_diff"— the more robust of the two percent summaries (see thespatial_responsedocs/vignette for why). - Default
metricforplot.sdmTMB_mesh_cv()changes from"sum_loglik"to"deltaNLL"(now that its sign is fixed — see above), which is easier to read at a glance than the raw summed log-likelihood. -
plot.sdmTMB_survey_comparison(type = "spatial")gains the samefill_prefixargument asplot.sdmTMB_model_comparison(). -
plot.sdmTMB_survey_comparison(type = "scatter")gains anannotate_fitargument (defaultTRUE): annotates each survey’s regression line with its fitted equation and R², coloured to match that survey. -
compare_models()andcompare_meshes()had the same silent-NA-CRS bug as thecompare_surveys()fix above; fixed the same way. Found by actually running every function’s roxygen example end-to-end (see below) — prior examples happened to always passobject_crsexplicitly, so this had never surfaced. -
bubble_map()’s legend was rendering as two separate, mismatched legends (one binned colour swatch legend, one continuous size legend), and printed an “Ignoring unknown labels: colour” warning. Fixed: thefill/colourandsizescales now use matchingguide = "bins"so ggplot2 merges them into one combined size+colour legend, and a redundantshapeaesthetic mapping (constant within each already-split zero/non-zero data subset, but was breaking the guide merge even withguide = "none") is now a fixed value per layer instead of a mapped-but-hidden aesthetic. - Every exported function now has a working
@examplesblock. SincesdmTMBis an Imports (not Depends) dependency, unqualified calls likepcodormake_mesh()fail outside a session withlibrary(sdmTMB)attached — one existing example (plot_residual_qq()) had this bug. Slow (model-fitting) examples are wrapped in\dontrun{}, which — unlike\donttest{}— is never run by this package’sR CMD check(even under--as-cran) but does run on the pkgdown site (run_dont_run = TRUEis now passed in.github/workflows/pkgdown.yaml). Seememory/examples-policy.md. -
sdmTMB_mesh_cv()(spatial block cross-validation across mesh cutoffs, viablockCV::cv_spatial()+sdmTMB::sdmTMB_cv()) is confirmed working end-to-end and now has a real, runnable example (was previously untested, with a placeholder example referencing an undefinedfit).