3
3
4
4
# ' Fit a spatial or spatiotemporal GLMM with TMB
5
5
# '
6
- # ' Fit a spatial or spatiotemporal Gaussian random field generalized linear
7
- # ' mixed effects model (GLMM) with the TMB (Template Model Builder) R package and
8
- # ' the SPDE (stochastic partial differential equation) approach. This can be
9
- # ' useful for (dynamic) species distribution models and relative abundance index
10
- # ' standardization among many other uses.
6
+ # ' Fit a spatial or spatiotemporal generalized linear mixed effects model (GLMM)
7
+ # ' with the TMB (Template Model Builder) R package and the SPDE (stochastic
8
+ # ' partial differential equation) approximation to Gaussian random fields.
11
9
# '
12
10
# ' @param formula Model formula. IID random intercepts are possible using
13
11
# ' \pkg{lme4} syntax, e.g., `+ (1 | g)` where `g` is a column of class
82
80
# ' \doi{10.1111/ecog.05176} and the [spatial trends
83
81
# ' vignette](https://pbs-assess.github.io/sdmTMB/articles/spatial-trend-models.html).
84
82
# ' Note this predictor should usually be centered to have mean zero and have a
85
- # ' standard deviation of approximately 1 and should likely also be included as
86
- # ' a main effect.
83
+ # ' standard deviation of approximately 1.
87
84
# ' **The spatial intercept is controlled by the `spatial` argument**; therefore,
88
85
# ' include or exclude the spatial intercept by setting `spatial = 'on'` or
89
86
# ' `'off'`. The only time when it matters whether `spatial_varying` excludes
97
94
# ' a name of the variable in the data frame. See the Details section below.
98
95
# ' @param offset A numeric vector representing the model offset *or* a character
99
96
# ' value representing the column name of the offset. In delta/hurdle models,
100
- # ' this applies only to the positive component. Usually a log
101
- # ' transformed variable.
97
+ # ' this applies only to the positive component. Usually a log transformed
98
+ # ' variable.
102
99
# ' @param extra_time Optional extra time slices (e.g., years) to include for
103
100
# ' interpolation or forecasting with the predict function. See the Details
104
101
# ' section below.
163
160
# ' An object (list) of class `sdmTMB`. Useful elements include:
164
161
# '
165
162
# ' * `sd_report`: output from [TMB::sdreport()]
166
- # ' * `gradients`: log likelihood gradients with respect to each fixed effect
163
+ # ' * `gradients`: marginal log likelihood gradients with respect to each fixed effect
167
164
# ' * `model`: output from [stats::nlminb()]
168
165
# ' * `data`: the fitted data
169
166
# ' * `mesh`: the object that was supplied to the `mesh` argument
202
199
# ' with `+ s(x, y)` or `+ t2(x, y)`; smooths can be specific to various factor
203
200
# ' levels, `+ s(x, by = group)`; the basis function dimensions may be specified,
204
201
# ' e.g. `+ s(x, k = 4)`; and various types of splines may be constructed such as
205
- # ' cyclic splines to model seasonality, `+ s(month, bs = "cc", k = 12)` (perhaps
206
- # ' with the `knots` argument also be supplied).
202
+ # ' cyclic splines to model seasonality (perhaps with the `knots` argument also
203
+ # ' be supplied).
207
204
# '
208
205
# ' **Threshold models**
209
206
# '
216
213
# ' `+ logistic(variable)`. This option models the relationship as a logistic
217
214
# ' function of the 50% and 95% values. This is similar to length- or size-based
218
215
# ' selectivity in fisheries, and is parameterized by the points at which f(x) =
219
- # ' 0.5 or 0.95. See the [threshold vignette](https://pbs-assess.github.io/sdmTMB/articles/threshold-models.html).
216
+ # ' 0.5 or 0.95. See the
217
+ # ' [threshold vignette](https://pbs-assess.github.io/sdmTMB/articles/threshold-models.html).
220
218
# '
221
219
# ' Note that only a single threshold covariate can be included and the same covariate
222
220
# ' is included in both components for the delta families.
@@ -236,16 +234,7 @@ NULL
236
234
# ' time slices with process error.
237
235
# '
238
236
# ' `extra_time` can also be used to fill in missing time steps for the purposes
239
- # ' of a random walk or AR(1) process if their inclusion makes the gaps between
240
- # ' time steps even.
241
- # '
242
- # ' **Index standardization**
243
- # '
244
- # ' For index standardization, you may wish to include `0 + as.factor(year)`
245
- # ' (or whatever the time column is called) in the formula. See a basic
246
- # ' example of index standardization in the relevant
247
- # ' [package vignette](https://pbs-assess.github.io/sdmTMB/articles/index-standardization.html).
248
- # ' You will need to specify the `time` argument. See [get_index()].
237
+ # ' of a random walk or AR(1) process if the gaps between time steps are uneven.
249
238
# '
250
239
# ' **Regularization and priors**
251
240
# '
@@ -279,11 +268,19 @@ NULL
279
268
# ' The main advantage of specifying such models using a delta family (compared
280
269
# ' to fitting two separate models) is (1) coding simplicity and (2) calculation
281
270
# ' of uncertainty on derived quantities such as an index of abundance with
282
- # ' [get_index()] using the generalized delta method within TMB. Also, parameters
283
- # ' can be shared across the models.
271
+ # ' [get_index()] using the generalized delta method within TMB. Also, selected
272
+ # ' parameters can be shared across the models.
284
273
# '
285
274
# ' See the [delta-model vignette](https://pbs-assess.github.io/sdmTMB/articles/delta-models.html).
286
275
# '
276
+ # ' **Index standardization**
277
+ # '
278
+ # ' For index standardization, you may wish to include `0 + as.factor(year)`
279
+ # ' (or whatever the time column is called) in the formula. See a basic
280
+ # ' example of index standardization in the relevant
281
+ # ' [package vignette](https://pbs-assess.github.io/sdmTMB/articles/index-standardization.html).
282
+ # ' You will need to specify the `time` argument. See [get_index()].
283
+ # '
287
284
# ' @references
288
285
# '
289
286
# ' **Main reference introducing the package to cite when using sdmTMB:**
305
302
# ' English, P., E.J. Ward, C.N. Rooper, R.E. Forrest, L.A. Rogers, K.L. Hunter,
306
303
# ' A.M. Edwards, B.M. Connors, S.C. Anderson. 2021. Contrasting climate velocity
307
304
# ' impacts in warm and cool locations show that effects of marine warming are
308
- # ' worse in already warmer temperate waters. In press at Fish and Fisheries.
305
+ # ' worse in already warmer temperate waters. Fish and Fisheries. 23(1) 239-255 .
309
306
# ' \doi{10.1111/faf.12613}.
310
307
# '
311
308
# ' *Discussion of and illustration of some decision points when fitting these
@@ -319,17 +316,18 @@ NULL
319
316
# ' *Application and description of threshold/break-point models:*
320
317
# '
321
318
# ' Essington, T.E. S.C. Anderson, L.A.K. Barnett, H.M. Berger, S.A. Siedlecki,
322
- # ' E.J. Ward. Advancing statistical models to reveal the effect of dissolved
323
- # ' oxygen on the spatial distribution of marine taxa using thresholds and a
324
- # ' physiologically based index. In press at Ecography. \doi{10.1111/ecog.06249}.
319
+ # ' E.J. Ward. 2022. Advancing statistical models to reveal the effect of
320
+ # ' dissolved oxygen on the spatial distribution of marine taxa using thresholds
321
+ # ' and a physiologically based index. Ecography. 2022: e06249
322
+ # ' \doi{10.1111/ecog.06249}.
325
323
# '
326
324
# ' *Application to fish body condition:*
327
325
# '
328
326
# ' Lindmark, M., S.C. Anderson, M. Gogina, M. Casini. Evaluating drivers of
329
327
# ' spatiotemporal individual condition of a bottom-associated marine fish.
330
328
# ' bioRxiv 2022.04.19.488709. \doi{10.1101/2022.04.19.488709}.
331
329
# '
332
- # ' *A number of sections of the original TMB model code were adapted from the
330
+ # ' *Several sections of the original TMB model code were adapted from the
333
331
# ' VAST R package:*
334
332
# '
335
333
# ' Thorson, J.T., 2019. Guidance for decisions using the Vector Autoregressive
@@ -363,13 +361,14 @@ NULL
363
361
# '
364
362
# ' # Build a mesh to implement the SPDE approach:
365
363
# ' mesh <- make_mesh(pcod_2011, c("X", "Y"), cutoff = 20)
366
- # ' # * this example uses a fairly coarse mesh so these examples run quickly
367
- # ' # * `cutoff` is the minimum distance between mesh vertices in units of the
364
+ # '
365
+ # ' # - this example uses a fairly coarse mesh so these examples run quickly
366
+ # ' # - 'cutoff' is the minimum distance between mesh vertices in units of the
368
367
# ' # x and y coordinates
369
- # ' # * ` cutoff = 10` or `cutoff = 15` might make more sense in applied situations
370
- # ' # for this dataset
371
- # ' # * or build any mesh in 'fmesher' and pass it to the `mesh` argument in `make_mesh()`
372
- # ' # * not needed if you will be turning off all spatial/spatiotemporal random fields
368
+ # ' # - ' cutoff = 10' might make more sense in applied situations for this dataset
369
+ # ' # - or build any mesh in 'fmesher' and pass it to the 'mesh' argument in make_mesh()`
370
+ # ' # - the mesh is not needed if you will be turning off all
371
+ # ' # spatial/spatiotemporal random fields
373
372
# '
374
373
# ' # Quick mesh plot:
375
374
# ' plot(mesh)
@@ -607,14 +606,10 @@ sdmTMB <- function(
607
606
spatiotemporal <- rep(" iid" , n_m )
608
607
}
609
608
610
-
611
609
if (is.null(time )) {
612
610
spatial_only <- rep(TRUE , n_m )
613
611
} else {
614
612
spatial_only <- ifelse(spatiotemporal == " off" , TRUE , FALSE )
615
- # if(all(spatiotemporal == "off")) {
616
- # cli_abort("Time needs to be null if spatiotemporal fields are not included")
617
- # }
618
613
}
619
614
620
615
if (is.list(spatial )) {
@@ -634,8 +629,6 @@ sdmTMB <- function(
634
629
}
635
630
636
631
if (! include_spatial && all(spatiotemporal == " off" ) || ! include_spatial && all(spatial_only )) {
637
- # message("Both spatial and spatiotemporal fields are set to 'off'.")
638
- # control$map_rf <- TRUE
639
632
no_spatial <- TRUE
640
633
if (missing(mesh )) {
641
634
mesh <- sdmTMB :: pcod_mesh_2011 # internal data; fake!
@@ -787,20 +780,6 @@ sdmTMB <- function(
787
780
# "As of version 0.3.1, sdmTMB turns off the constant spatial field `omega_s` when `spatial_varying` is specified so that the intercept or factor-level means are fully described by the spatially varying random fields `zeta_s`.")
788
781
cli_inform(paste(msg , collapse = " " ))
789
782
}
790
- # if (!omit_spatial_intercept & !length(attr(z_i, "contrasts"))) {
791
- # msg <- c("The spatial intercept is now in the first element of the spatially varying random fields `zeta_s` instead of the constant spatial random field `omega_s`. This change in the output format occurred in version 0.3.1.")
792
- # cli_inform(msg)
793
- # }
794
- # .int <- sum(grep("(Intercept)", colnames(z_i)) > 0)
795
- # if (.int && !omit_spatial_intercept) {
796
- # # msg <- c("Detected an intercept in `spatial_varying`.",
797
- # # "Make sure you have `spatial = 'off'` set since this also represents a spatial intercept.")
798
- # # cli_warn(msg)
799
- # # actually, just do it!
800
- # omit_spatial_intercept <- TRUE
801
- # include_spatial <- TRUE
802
- # spatial <- "on"
803
- # }
804
783
.int <- grep(" (Intercept)" , colnames(z_i ))
805
784
if (sum(.int ) > 0 ) z_i <- z_i [,- .int ,drop = FALSE ]
806
785
spatial_varying <- colnames(z_i )
@@ -1042,7 +1021,6 @@ sdmTMB <- function(
1042
1021
1043
1022
# TODO: make this cleaner
1044
1023
X_ij_list <- list ()
1045
- # X_ij_array <- array(data = NA, dim = c(nrow(X_ij[[1]]), ncol(X_ij[[1]]), n_m))
1046
1024
for (i in seq_len(n_m )) X_ij_list [[i ]] <- X_ij [[i ]]
1047
1025
1048
1026
n_t <- length(unique(data [[time ]]))
@@ -1174,8 +1152,6 @@ sdmTMB <- function(
1174
1152
tmb_params $ b_j <- stats :: coef(temp )
1175
1153
}
1176
1154
1177
- # if (delta && !is.null(thresh$threshold_parameter)) cli_abort("Thresholds not implemented with delta models yet.") # TODO DELTA
1178
-
1179
1155
# Map off parameters not needed
1180
1156
tmb_map <- map_all_params(tmb_params )
1181
1157
tmb_map $ b_j <- NULL
@@ -1203,14 +1179,6 @@ sdmTMB <- function(
1203
1179
tmb_map $ ln_phi <- as.factor(tmb_map $ ln_phi )
1204
1180
if (! is.null(thresh [[1 ]]$ threshold_parameter )) tmb_map $ b_threshold <- NULL
1205
1181
1206
- # optional models on spatiotemporal sd parameter
1207
- # if (est_epsilon_re == 0L) {
1208
- # tmb_map <- c(tmb_map,
1209
- # list(
1210
- # ln_epsilon_re_sigma = factor(rep(NA, n_m)),
1211
- # epsilon_re = factor(rep(NA, tmb_data$n_t))
1212
- # ))
1213
- # }
1214
1182
if (est_epsilon_re == 1L ) {
1215
1183
tmb_map <- unmap(tmb_map , c(" ln_epsilon_re_sigma" ," epsilon_re" ))
1216
1184
}
@@ -1222,7 +1190,7 @@ sdmTMB <- function(
1222
1190
1223
1191
1224
1192
original_tmb_data <- tmb_data
1225
- # # much faster on first phase!?
1193
+ # much faster on first phase!?
1226
1194
tmb_data $ no_spatial <- 1L
1227
1195
# tmb_data$include_spatial <- 0L
1228
1196
tmb_data $ include_spatial <- rep(0L , length(spatial )) # for 1st phase
@@ -1287,11 +1255,6 @@ sdmTMB <- function(
1287
1255
if (reml ) tmb_random <- c(tmb_random , " b_j" )
1288
1256
if (reml && delta ) tmb_random <- c(tmb_random , " b_j2" )
1289
1257
1290
- # # if (est_epsilon_model >= 2) {
1291
- # # # model 2 = re model, model 3 = loglinear-re
1292
- # # tmb_random <- c(tmb_random, "epsilon_rw")
1293
- # # }
1294
-
1295
1258
if (sm $ has_smooths ) {
1296
1259
if (reml ) tmb_random <- c(tmb_random , " bs" )
1297
1260
tmb_random <- c(tmb_random , " b_smooth" ) # smooth random effects
@@ -1380,8 +1343,6 @@ sdmTMB <- function(
1380
1343
prof <- c(" b_j" )
1381
1344
if (delta ) prof <- c(prof , " b_j2" )
1382
1345
1383
-
1384
-
1385
1346
out_structure <- structure(list (
1386
1347
data = data ,
1387
1348
spde = spde ,
@@ -1445,7 +1406,6 @@ sdmTMB <- function(
1445
1406
out_structure $ do_index <- FALSE
1446
1407
}
1447
1408
1448
-
1449
1409
tmb_obj <- TMB :: MakeADFun(
1450
1410
data = tmb_data , parameters = tmb_params , map = tmb_map ,
1451
1411
profile = if (control $ profile ) prof else NULL ,
@@ -1546,23 +1506,6 @@ set_limits <- function(tmb_obj, lower, upper, loc = NULL, silent = TRUE) {
1546
1506
.upper [" ar1_phi" ] <- stats :: qlogis((0.999 + 1 ) / 2 )
1547
1507
}
1548
1508
1549
- # if (!is.null(loc) && !"ln_kappa" %in% union(names(lower), names(upper))) {
1550
- # .dist <- stats::dist(loc)
1551
- # range_limits <- c(min(.dist) * 0.25, max(.dist) * 4)
1552
- # .upper[names(.upper) == "ln_kappa"] <- log(sqrt(8) / range_limits[1])
1553
- # .lower[names(.upper) == "ln_kappa"] <- log(sqrt(8) / range_limits[2])
1554
- # message("Setting limits on range to ",
1555
- # round(range_limits[1], 1), " and ",
1556
- # round(range_limits[2], 1), ":\n",
1557
- # "half the minimum and twice the maximum knot distance."
1558
- # )
1559
- # if (.upper[names(.upper) == "ln_kappa"][1] <= tmb_obj$par[["ln_kappa"]][1]) {
1560
- # .upper[names(.upper) == "ln_kappa"] <- tmb_obj$par[["ln_kappa"]][1] + 0.1
1561
- # }
1562
- # if (.lower[names(.lower) == "ln_kappa"][1] >= tmb_obj$par[["ln_kappa"]][1]) {
1563
- # .lower[names(.lower) == "ln_kappa"] <- tmb_obj$par[["ln_kappa"]][1] - 0.1
1564
- # }
1565
- # }
1566
1509
list (lower = .lower , upper = .upper )
1567
1510
}
1568
1511
@@ -1624,7 +1567,6 @@ check_irregalar_time <- function(data, time, spatiotemporal, time_varying) {
1624
1567
find_missing_time <- function (x ) {
1625
1568
if (! is.factor(x )) {
1626
1569
ti <- sort(unique(x ))
1627
- # mindiff <- min(diff(ti))
1628
1570
mindiff <- 1L
1629
1571
allx <- seq(min(ti ), max(ti ), by = mindiff )
1630
1572
setdiff(allx , ti )
0 commit comments